Slide 1

Slide 1 text

Testing in JavaScript Radoslav Stankov 27/08/2017

Slide 2

Slide 2 text

Radoslav Stankov @rstankov http://rstankov.com http://github.com/rstankov

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

https://speakerdeck.com/rstankov/testing-in-javascript

Slide 6

Slide 6 text

Why do automated testing?

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

• code task 1 • check task 1
 • code task 2 • check task 2 • check task 1
 • code task 3 • check task 3 • check task 2 • check task 1
 • code task 4 • check task 4 • check task 3 • check task 2 • check task 1 • code task 1 • code task 1 test • run tests
 • code task 2 • code task 2 test • run tests
 • code task 3 • code task 3 test • run tests
 • code task 4 • code task 4 test • run tests Manual Automatic

Slide 16

Slide 16 text

• code task 1 • check task 1
 • code task 2 • check task 2 • check task 1
 • code task 3 • check task 3 • check task 2 • check task 1
 • code task 4 • check task 4 • check task 3 • check task 2 • check task 1 • code task 5 • check task 4 • check task 4 • check task 3 • check task 2 • code task 1 • code task 1 test • run tests
 • code task 2 • code task 2 test • run tests
 • code task 3 • code task 3 test • run tests
 • code task 4 • code task 4 test • run tests • code task 5 • code task 5 test • run tests Manual Automatic

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

! Safety net " Reduces bugs in new or existing features # Forces you to understand the features and communication between components $ Reduces the cost for new features % Making code testable, decreases complexity and increases modularity

Slide 19

Slide 19 text

Terminology

Slide 20

Slide 20 text

System under test (SUT) Tests Unit test Integration test Acceptance test SUD

Slide 21

Slide 21 text

Unit Test Integration Test Acceptance Test Tests one object Tests one “component” Tests the whole stack Isolated with mocks Some times uses stubs Uses the UI
 Expresses domain terms Fast Slow Very slow Helps for good design Helps for verification Helps for verification

Slide 22

Slide 22 text

Unit Test Integration Test Acceptance Test Tests one object Tests one “component” Tests the whole stack Isolated with mocks Some times uses stubs Uses the UI
 Expresses domain terms Fast Slow Very slow Helps for good design Helps for verification Helps for verification

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

“I don’t have to think how to run my tests.“ Easy to run

Slide 25

Slide 25 text

“Running tests, should not distract me from the problem I’m solving.“ Quick

Slide 26

Slide 26 text

“I should not spend any energy if understanding the test.“ Simple

Slide 27

Slide 27 text

“I should have confidence, that when my test pass, my code is most probably is working.” Reliable

Slide 28

Slide 28 text

“I should not rewrite my tests, every time I change something.” Flexible

Slide 29

Slide 29 text

“When a test fails I should know exactly why?” Localisable

Slide 30

Slide 30 text

“One test should not depend on other test” Isolated

Slide 31

Slide 31 text

describe(Calculator, () => { describe('evaluate', () => { it('handles "1 + 1" input', () => { const calculator = new Calculator('1 + 1'); const value = calculator.evaluate(); expect(value).toEqual(2); }); }); });

Slide 32

Slide 32 text

describe(Calculator, () => { describe('evaluate', () => { it('handles "1 + 1" input', () => { const calculator = new Calculator('1 + 1'); const value = calculator.evaluate(); expect(value).toEqual(2); }); }); });

Slide 33

Slide 33 text

describe(Calculator, () => { describe('evaluate', () => { it('handles "1 + 1" input', () => { const calculator = new Calculator('1 + 1'); const value = calculator.evaluate(); expect(value).toEqual(2); }); }); }); Test Suite

Slide 34

Slide 34 text

describe(Calculator, () => { describe('evaluate', () => { it('handles "1 + 1" input', () => { const calculator = new Calculator('1 + 1'); const value = calculator.evaluate(); expect(value).toEqual(2); }); }); }); Sub Test Suite

Slide 35

Slide 35 text

describe(Calculator, () => { describe('evaluate', () => { it('handles "1 + 1" input', () => { const calculator = new Calculator('1 + 1'); const value = calculator.evaluate(); expect(value).toEqual(2); }); }); }); Test Case

Slide 36

Slide 36 text

describe(Calculator, () => { describe('evaluate', () => { it('handles "1 + 1" input', () => { const calculator = new Calculator('1 + 1'); const value = calculator.evaluate(); expect(value).toEqual(2); }); }); }); Assertion

Slide 37

Slide 37 text

describe(Calculator, () => { describe('evaluate', () => { beforeEach(() => { console.log('test case start'); }); beforeAfter(() => { console.log('test case end'); }); it('handles "1 + 1" input', () => { const calculator = new Calculator('1 + 1'); const value = calculator.evaluate(); expect(value).toEqual(2); }); }); });

Slide 38

Slide 38 text

describe(Calculator, () => { describe('evaluate', () => { beforeEach(() => { console.log('test case start'); }); beforeAfter(() => { console.log('test case end'); }); it('handles "1 + 1" input', () => { const calculator = new Calculator('1 + 1'); const value = calculator.evaluate(); expect(value).toEqual(2); }); }); }); Setup

Slide 39

Slide 39 text

describe(Calculator, () => { describe('evaluate', () => { beforeEach(() => { console.log('test case start'); }); beforeAfter(() => { console.log('test case end'); }); it('handles "1 + 1" input', () => { const calculator = new Calculator('1 + 1'); const value = calculator.evaluate(); expect(value).toEqual(2); }); }); }); Teardown

Slide 40

Slide 40 text

Four Phase Test

Slide 41

Slide 41 text

Four Phase Test Setup

Slide 42

Slide 42 text

Four Phase Test Setup Action

Slide 43

Slide 43 text

Four Phase Test Setup Action Assertion

Slide 44

Slide 44 text

Four Phase Test Setup Action Assertion Teardown

Slide 45

Slide 45 text

Four Phase Test Setup Action Assertion Teardown

Slide 46

Slide 46 text

describe(Calculator, () => { describe('evaluate', () => { it('handles "1 + 1" input', () => { const calculator = new Calculator('1 + 1'); const value = calculator.evaluate(); expect(value).toEqual(2); }); it('handles "2 - 1" input', () => { const calculator = new Calculator('2 - 1'); const value = calculator.evaluate(); expect(value).toEqual(1); }); it('handles "2 * 3" input', () => { const calculator = new Calculator('2 * 3'); const value = calculator.evaluate(); expect(value).toEqual(6); }); }); });

Slide 47

Slide 47 text

describe(Calculator, () => { describe('evaluate', () => { it('handles "1 + 1" input', () => { const calculator = new Calculator('1 + 1'); const value = calculator.evaluate(); expect(value).toEqual(2); }); it('handles "2 - 1" input', () => { const calculator = new Calculator('2 - 1'); const value = calculator.evaluate(); expect(value).toEqual(1); }); it('handles "2 * 3" input', () => { const calculator = new Calculator('2 * 3'); const value = calculator.evaluate(); expect(value).toEqual(6); }); }); });

Slide 48

Slide 48 text

describe(calculate.name, () => { describe('operations', () => { it('handles noop', () => { expect(calculate('12')).toEqual('12'); }); it('handles "1 + 1" input', () => { expect(calculate('1 + 1')).toEqual('2'); }); it('handles "1 - 1" input', () => { expect(calculate('2 - 1')).toEqual('1'); }); it('handles "2 * 3" input', () => { expect(calculate('2 * 3')).toEqual('6'); }); }); });

Slide 49

Slide 49 text

describe(calculate.name, () => { describe('operations', () => { it('handles noop', () => { expect(calculate('12')).toEqual('12'); }); it('handles "1 + 1" input', () => { expect(calculate('1 + 1')).toEqual('2'); }); it('handles "1 - 1" input', () => { expect(calculate('2 - 1')).toEqual('1'); }); it('handles "2 * 3" input', () => { expect(calculate('2 * 3')).toEqual('6'); }); }); });

Slide 50

Slide 50 text

Automated Testing Test Driven Development

Slide 51

Slide 51 text

Test Driven Development

Slide 52

Slide 52 text

Test Driven Development 1 Write test ... for code which is not written yet

Slide 53

Slide 53 text

Test Driven Development 1 Write test ... for code which is not written yet 2 Write code ... as little as require for the test

Slide 54

Slide 54 text

Test Driven Development 1 Write test ... for code which is not written yet 3 Refactor ... remove duplications 2 Write code ... as little as require for the test

Slide 55

Slide 55 text

Test Driven Development 1 Write test ... for code which is not written yet 3 Refactor ... remove duplications 2 Write code ... as little as require for the test

Slide 56

Slide 56 text

No content

Slide 57

Slide 57 text

No content

Slide 58

Slide 58 text


 calculate('1 + 1') Ⱦ'2' calculate('2 - 1') Ⱦ'1' calculate('2 * 2') Ⱦ'4'

Slide 59

Slide 59 text

describe(calculate.name, () => {
 it('handles "1 + 1"'); it('handles "2 - 1"'); it('handles "2 * 2"'); });

Slide 60

Slide 60 text

Code Test

Slide 61

Slide 61 text

describe(calculate.name, () => { it('handles "1 + 1"'); it('handles "2 - 1"'); it('handles "2 * 2"'); }); Code Test

Slide 62

Slide 62 text

describe(calculate.name, () => { it('handles "1 + 1"', () => { expect(calculate('1 + 1')).toEqual(2); }); it('handles "2 - 1"'); it('handles "2 * 2"'); }); Code Test ✗

Slide 63

Slide 63 text

describe(calculate.name, () => { it('handles "1 + 1"', () => { expect(calculate('1 + 1')).toEqual(2); }); it('handles "2 - 1"'); it('handles "2 * 2"'); }); export default function calculate(memory) { return 2; } Code Test ✔

Slide 64

Slide 64 text

describe(calculate.name, () => { it('handles "1 + 1"', () => { expect(calculate('1 + 1')).toEqual(2); }); it('handles "2 - 1"', () => { expect(calculate('2 - 1')).toEqual(1); }); it('handles "2 * 2"'); }); export default function calculate(memory) { return 2; } Code Test ✗

Slide 65

Slide 65 text

describe(calculate.name, () => { it('handles "1 + 1"', () => { expect(calculate('1 + 1')).toEqual(2); }); it('handles "2 - 1"', () => { expect(calculate('2 - 1')).toEqual(1); }); it('handles "2 * 2"'); }); export default function calculate(memory) { const [a, operand, b] = memory.split(' '); if (operand === '+') { return Number(a) + Number(b); } else { return Number(a) - Number(b); } } Code Test ✔

Slide 66

Slide 66 text

describe(calculate.name, () => { it('handles "1 + 1"', () => { expect(calculate('1 + 1')).toEqual(2); }); it('handles "2 - 1"', () => { expect(calculate('2 - 1')).toEqual(1); }); it('handles "2 * 2"', () => { expect(calculate('2 * 2')).toEqual(4); }); }); export default function calculate(memory) { const [a, operand, b] = memory.split(' '); if (operand === '+') { return Number(a) + Number(b); } else { return Number(a) - Number(b); } } Code Test ✗

Slide 67

Slide 67 text

describe(calculate.name, () => { it('handles "1 + 1"', () => { expect(calculate('1 + 1')).toEqual(2); }); it('handles "2 - 1"', () => { expect(calculate('2 - 1')).toEqual(1); }); it('handles "2 * 2"', () => { expect(calculate('2 * 2')).toEqual(4); }); }); export default function calculate(memory) { const [a, operand, b] = memory.split(' '); if (operand === '+') { return Number(a) + Number(b); } else if (operand === '-') { return Number(a) - Number(b); } else { return Number(a) * Number(b); } } Code Test ✔

Slide 68

Slide 68 text

describe(calculate.name, () => { it('handles "1 + 1"', () => { expect(calculate('1 + 1')).toEqual(2); }); it('handles "2 - 1"', () => { expect(calculate('2 - 1')).toEqual(1); }); it('handles "2 * 2"', () => { expect(calculate('2 * 2')).toEqual(4); }); }); const OPERATIONS = { '+': (a, b) => a + b, '-': (a, b) => a - b, '*': (a, b) => a * b, }; export default function calculate(memory) { const [a, operand, b] = memory.split(' '); const operation = OPERATIONS[operand]; return operation(Number(a), Number(b)); } Code Test ✔

Slide 69

Slide 69 text

describe(calculate.name, () => { describe('operations', () => { it('handles noop', () => { expect(calculate('12')).toEqual('12'); }); it('handles "1 + 1" input', () => { expect(calculate('1 + 1')).toEqual('2'); }); it('handles "1 - 1" input', () => { expect(calculate('2 - 1')).toEqual('1'); }); it('handles "2 * 3" input', () => { expect(calculate('2 * 3')).toEqual('6'); }); }); describe('edge cases', () => { it('handles zero in input', () => { expect(calculate('1 + 0')).toEqual('1'); }); it('handles larger number in input', () => { expect(calculate('123 + 456')).toEqual(new String(123 + 456)); }); it('handles multiple operations input', () => { expect(calculate('1 + 1 + 1')).toEqual('3');

Slide 70

Slide 70 text

it('handles "2 * 3" input', () => { expect(calculate('2 * 3')).toEqual('6'); }); }); describe('edge cases', () => { it('handles zero in input', () => { expect(calculate('1 + 0')).toEqual('1'); }); it('handles larger number in input', () => { expect(calculate('123 + 456')).toEqual(new String(123 + 456)); }); it('handles multiple operations input', () => { expect(calculate('1 + 1 + 1')).toEqual('3'); }); it('handles incomplete operation input', () => { expect(calculate('1 + ')).toEqual('1'); }); it('handles negative values', () => { expect(calculate('-1 + 2')).toEqual('1'); }); it('raises on invalid operand', () => { expect(() => calculate('1 % 2')).toThrowError('Invalid "%" operand'); }); }); });

Slide 71

Slide 71 text

Testing React

Slide 72

Slide 72 text

No content

Slide 73

Slide 73 text

export default class Calculator extends React.Component { state = { memory: '0', }; render() { return ( {this.state.memory.trim()} 1 2 3 4 5 6 7 8 9 C 0 ← = + - *

Slide 74

Slide 74 text

2 3 4 5 6 7 8 9 C 0 ← = + - * ); } add = value => () => this.setMemory(addToMemory(this.state.memory, value)); remove = () => this.setMemory(removeFromMemory(this.state.memory)); reset = () => this.setMemory('0'); calculate = () => this.setMemory(calculate(this.state.memory)); setMemory = memory => this.setState({ memory }); }

Slide 75

Slide 75 text

}; render() { return ( {this.state.memory.trim()} 1 2 3 4 5 6 7 8 9 C 0 ← = + - * ); }

Slide 76

Slide 76 text

{this.state.memory.trim()} 1 2 3 4 5 6 7 8 9 C 0 ← = + - *

Slide 77

Slide 77 text

import React from 'react'; import Component from './index'; import { shallow, mount } from 'enzyme'; import Memory from './Memory'; describe(Component.name, () => { it('renders memory', () => { const component = shallow(); expect(component.contains(0)).toEqual(true); }); });

Slide 78

Slide 78 text

{this.state.memory.trim()} 1 2 3 4 5 6 7 8 9 C 0 ← = + - *

Slide 79

Slide 79 text

{this.state.memory.trim()} 1 2 3 4 5 6 7 8 9 C 0 ← = + - *

Slide 80

Slide 80 text

it('can add digits and operands', () => { const component = mount(); component.find('[data-test="1"]').simulate('click'); component.find('[data-test="+"]').simulate('click'); component.find('[data-test="1"]').simulate('click'); expect(component.contains(1 + 1)).toEqual(true); }); it('can sum numbers', () => { const component = mount(); component.find('[data-test="1"]').simulate('click'); component.find('[data-test="+"]').simulate('click'); component.find('[data-test="1"]').simulate('click'); component.find('[data-test="calculate"]').simulate('click'); expect(component.contains(2)).toEqual(true); }); it('can remove digits and operands', () => { const component = mount(); component.find('[data-test="1"]').simulate('click'); component.find('[data-test="+"]').simulate('click'); component.find('[data-test="1"]').simulate('click'); component.find('[data-test="remove"]').simulate('click'); expect(component.contains(1 +)).toEqual(true); });

Slide 81

Slide 81 text

No content

Slide 82

Slide 82 text

it('can add digits and operands', () => { const component = mount(); component.find('[data-test="1"]').simulate('click'); component.find('[data-test="+"]').simulate('click'); component.find('[data-test="1"]').simulate('click'); expect(component).toContainReact(1 + 1); }); it('can sum numbers', () => { const component = mount(); component.find('[data-test="1"]').simulate('click'); component.find('[data-test="+"]').simulate('click'); component.find('[data-test="1"]').simulate('click'); component.find('[data-test="calculate"]').simulate('click'); expect(component).toContainReact(2); }); it('can remove digits and operands', () => { const component = mount(); component.find('[data-test="1"]').simulate('click'); component.find('[data-test="+"]').simulate('click'); component.find('[data-test="1"]').simulate('click'); component.find('[data-test="remove"]').simulate('click'); expect(component).toContainReact(1 +); });

Slide 83

Slide 83 text

No content

Slide 84

Slide 84 text

No content

Slide 85

Slide 85 text

Mocks

Slide 86

Slide 86 text

No content

Slide 87

Slide 87 text

Used as a simpler implementation, e.g. using an in-memory database in the tests instead of doing real database access. Fake

Slide 88

Slide 88 text

Used when a parameter is needed for the tested method but without actually needing to use the parameter. Dummy object

Slide 89

Slide 89 text

Test stubs are objects with pre-programmed behaviour. Stub

Slide 90

Slide 90 text

Records arguments, return value, the value of this and exception thrown (if any) for all its calls. Spy

Slide 91

Slide 91 text

Mocks are fake methods (like spies) with pre- programmed behaviour (like stubs) as well as pre- programmed expectations. A mock will fail your test if it is not used as expected. Mock

Slide 92

Slide 92 text

Test double Stub Spy Mock Fake Dummy

Slide 93

Slide 93 text


 http://sinonjs.org


Slide 94

Slide 94 text

import { connect } from 'react-redux'; import { setNotification } from 'modules/notification'; export class NotificationButton extends React.Component { render() { return Trigger Alert; } handleClick = () => { this.props.setNotification(this.props.notification); }; } export default connect(null, { setNotification })(NotificationButton);

Slide 95

Slide 95 text

describe(Component.name, () => { it('triggers `setNotification` onClick', () => { const spy = sinon.spy(); const component = shallow( , ); component.find('button').simulate('click'); expect(spy).toHaveBeenCalledWith('test'); }); });

Slide 96

Slide 96 text

sinon.useFakeTimers() sinon.useFakeXMLHttpRequest() sinon.stub() sinon.spy() sinon.mock(myAPI)

Slide 97

Slide 97 text

Tools

Slide 98

Slide 98 text

$ https://github.com/facebook/jest " https://github.com/jasmine/jasmine ! https://github.com/mochajs/mocha & https://github.com/chaijs/chai # https://github.com/sinonjs/sinon ' https://github.com/airbnb/enzyme/ ( https://github.com/producthunt/chai-enzyme % https://github.com/graphcool/chromeless Tools

Slide 99

Slide 99 text

Books

Slide 100

Slide 100 text

No content

Slide 101

Slide 101 text

No content

Slide 102

Slide 102 text

No content

Slide 103

Slide 103 text

Thanks )

Slide 104

Slide 104 text

https://github.com/rstankov/talks-code

Slide 105

Slide 105 text

https://speakerdeck.com/rstankov/testing-in-javascript