Date post: | 15-Jul-2015 |
Category: |
Technology |
Upload: | lars-thorup |
View: | 317 times |
Download: | 1 times |
Advanced JavaScriptUnit TestingLars ThorupZeaLake Software Consulting
April, 2015
Who is Lars Thorup?
● Software developer/architect● JavaScript, C#● Test Driven Development● Continuous Integration
● Coach: Teaching TDD and continuous integration
● Founder of ZeaLake
● @larsthorup
Best practices for JavaScript unit testing● Asynchronous code: callbacks and promises
● Time and timers
● The DOM
● Ajax
● Cross-browser testing
● Leak detection
● Continuous testing
Asynchronous code: callbacks● How to wait for the callback?
● Call done() when done! [Mocha, Jasmine]
https://github.com/larsthorup/mha2015-demo
it('should eventually return a+b', function () { code.addSlow(4, 5, function (result) { result.should.equal(9); });});
it('should eventually return a+b', function (done) { code.addSlow(4, 5, function (result) { result.should.equal(9); done(); });});
When this is called the test has
already ended
Asynchronous code: promises● Return a promise from the test [Mocha]
● Use promise matchers [chai-as-promised]
it('should eventually return a+b', function () { return code.adding(4, 5).should.become(9);});
https://github.com/larsthorup/mha2015-demo
Time and timers● How to test delayed behavior fast?
● Fake the built-in timer! [Sinon, Jasmine]
beforeEach(function () { this.sinon = sinon.sandbox.create(); this.sinon.useFakeTimers();});
afterEach(function () { this.sinon.restore();});
it('should eventually return a+b', function () { var adding = code.adding(4, 5); this.sinon.clock.tick(500); return adding.should.become(9);});
https://github.com/larsthorup/mha2015-demo
The DOM● Don't copy real markup
● Use the real thing if fast+easy (client side templating, RequireJS)● Else inject minimal fake markup
● Use a fragment, or insert a fixture, don't use document
https://github.com/larsthorup/jsdevenv-mocha-require/blob/master/src/test/js/page/weather.test.js
it('listens', function () { var context = $('<div><input type="text" id="city" /></div>'); weather.listen(context); var city = context.find('#city'); city.val('San Francisco'); city.trigger('change'); expect(weather.fetch.calledWith('San Francisco')).to.equal(true);});
CSS / responsive design● Render into an iframe, then adjust size [quixote]
beforeEach(function () { iframe = $('<iframe></iframe>').appendTo(fixture); context = $(iframe.get(0).contentDocument); $('<style></style>').text(menuCss).appendTo(context.find('head')); var menu = $(multiline(function () {/* <ul class="menu"> <li>Item 1</li> <li>Item 2</li> </ul> */})).appendTo(context.find('body')); items = menu.find('li');});
it('should turn horizontal when wide', function () { iframe.attr('width', '401px'); expect(items.eq(0).offset().left).to.be.below(items.eq(1).offset().left); expect(items.eq(0).offset().top).to.equal(items.eq(1).offset().top);});
https://github.com/larsthorup/jsdevenv-mocha-require/blob/master/src/test/js/style/menu-responsive.test.js
Ajax● Don't call any services
● Slow, fragile, setup heavy
● Mock or fake server responses [Sinon, mockjax]
https://github.com/larsthorup/jsdevenv-mocha-require/blob/master/src/test/js/page/weather.test.js
it('fetches', function (done) { $.mockjax({ url: 'http://api.openweathermap.org/data/2.5/weather', data: {q: 'Denver'}, responseText: { weather: [{ description: 'sun' }] }, responseTime: 1 }); var fetching = weather.fetch('Denver'); fetching.then(function (data) { expect(data.text).to.equal('sun'); done(); });});
Ajax - generate mocks from real responses
● zealake.com/2015/01/05/unit-test-your-service-integration-layer/
https://github.com/larsthorup/mars/blob/master/test/util/api.proxy.js
https://github.com/larsthorup/mars/tree/master/demo/test/api-faker.js
Cross-browser testing● Use Karma to run your tests in real browsers
● Also valuable for debugging
● Use browser services for continuous integration● BrowserStack● SauceLabs
https://github.com/larsthorup/jsdevenv-mocha-require/blob/master/Gruntfile.js
Leak detection● To ensure that tests don't influence each other
● To make our code more reusable
● Use global beforeEach() and afterEach() [Mocha, Jasmine]
● Sample before, verify after● window / global properties● DOM elements● localstorage keys
Wallaby.js● Continuous Testing
● For JetBrains IDE's● Visual Studio is in progress
● wallabyjs.com