Slide 1

Slide 1 text

QUIRKS AND TWISTS OF UNIT TESTING JAVASCRIPT Pavels Jelisejevs, C.T.Co

Slide 2

Slide 2 text

WHY DO WE EVEN NEED UNIT TESTS?

Slide 3

Slide 3 text

➤Reduces the risk of regressions ➤Enforces modularity and complexity constraints ➤Reduces maintenance costs

Slide 4

Slide 4 text

Unit testing requires you to understand how your code works.

Slide 5

Slide 5 text

TESTING JAVASCRIPT

Slide 6

Slide 6 text

WHAT’S SO SPECIAL ABOUT JAVASCRIPT ➤Asynchrony ➤Time ➤User interaction ➤DOM and other APIs

Slide 7

Slide 7 text

TOOLS OF THE TRADE Istanbul code coverage

Slide 8

Slide 8 text

LET’S GET PRACTICAL

Slide 9

Slide 9 text

A TRIVIAL EXAMPLE function add(number1, number2) { return number1 + number2; } describe('add()', function() { it('should return the sum of the given numbers', function() { expect(add(1, 2)).toEqual(3); }); });

Slide 10

Slide 10 text

ADDING MOCKS function redirect(url) { window.location.replace(url); } it('redirect() should redirect to the given URL', function() { spyOn(window.location, 'replace'); redirect('http://google.com'); expect(window.location.replace).toHaveBeenCalledWith(' http://google.com'); });

Slide 11

Slide 11 text

MOCKING GLOBAL FUNCTIONS it('should mock the Date() constructor', function() { var oldDate = Date; spyOn(window, 'Date').and.callFake(function() { return new oldDate('2014-03-30'); }); var d = new Date(); expect(d.getFullYear()).toEqual(2014); });

Slide 12

Slide 12 text

TIMEOUTS it('setTimeout() should call a function after some time', function() { var spy = jasmine.createSpy(); setTimeout(spy, 200); expect(spy).toHaveBeenCalled(); });

Slide 13

Slide 13 text

TIMEOUTS it('setTimeout() should call a function after some time', function() { jasmine.clock().install(); var spy = jasmine.createSpy(); setTimeout(spy, 200); jasmine.clock().tick(200); expect(spy).toHaveBeenCalled(); jasmine.clock().uninstall(); });

Slide 14

Slide 14 text

DOM ELEMENTS it('offsetHeight contains the height of an element', function() { var div = document.createElement('div'); div.innerHTML = 'Unit test all the things!'; expect(div.offsetHeight).toBeGreaterThan(0); });

Slide 15

Slide 15 text

DOM ELEMENTS it('offsetHeight contains the height of an element', function() { var div = document.createElement('div'); div.innerHTML = 'Unit test all the things!'; document.body.appendChild(div); expect(div.offsetHeight).toBeGreaterThan(0); div.remove(); });

Slide 16

Slide 16 text

SIMULATING EVENTS WITH JQUERY.SIMULATE() it('jQuery.simulate() should simulate a keydown event', function() { var spy = jasmine.createSpy(); var div = $('
').on('keydown', function() { spy(); }); div.simulate('keydown', { keyCode: 84 }); expect(spy).toHaveBeenCalled(); });

Slide 17

Slide 17 text

MODULES, BROWSERIFY AND REQUIRE()

Slide 18

Slide 18 text

THE MODULE var bar = require('./bar'); module.exports = function() { bar.buz(); }

Slide 19

Slide 19 text

THE REQUIRE() PROBLEM ➤Browserify allows to bundle modules using its own implementation of require() similar to node.js ➤We need to be able to mock libraries included by require() ➤This can be done in two ways: creating a proxy for require() or monkey patching

Slide 20

Slide 20 text

WRAPPING REQUIRE() WITH PROXYQUIRE var proxyquire = require('proxyquireify')(require); var stubs = { './bar': { buz: function () { return 'Buz!'; } } }; var foo = proxyquire('./foo', stubs);

Slide 21

Slide 21 text

MONKEY PATCHING WITH REWIREIFY() var foo = require('./foo'); foo.__set__('./bar', { buz: function () { return 'Buz!'; } });

Slide 22

Slide 22 text

A WORD OF ADVICE

Slide 23

Slide 23 text

YOUR TESTS SHOULD BE ➤Granular - each test should check a separate feature ➤Independent - tests should not rely on each other ➤Easy to write - as much as the rest of the code ➤Fast - better performance means more tests runs

Slide 24

Slide 24 text

WHAT TO DO IF YOU’RE STUCK ➤See other tests for example ➤Learn from 3rd party code vendors ➤Few tests are still better then no tests at all

Slide 25

Slide 25 text

THANK YOU