Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Workshop 5: JavaScript testing

626 views

Published on

Workshop JavaScript Testing. Frameworks. Client vs Server Testing. Jasmine. Chai. Nock. Sinon. Spec Runners: Karma. TDD. Code coverage. Building a testable JS app.

Presentado por ing: Raúl Delgado y Mario García

Published in: Software
  • Be the first to comment

  • Be the first to like this

Workshop 5: JavaScript testing

  1. 1. Front End Workshops VI.JavaScript testing. Client vs. Server testing. Test Driven Development Raúl Delgado Astudillo rdelgado@visual-engin.com Mario García Martín mgarcia@visual-engin.com
  2. 2. JavaScript testing “Testing is an infinite process of comparing the invisible to the ambiguous in order to avoid the unthinkable happening to the anonymous.” — James Bach
  3. 3. What is a test? Type some code Open and load the browser Prove functionality A test is (simply) the validation of an expectation. Manual testing... ...is NOT enough!
  4. 4. Can we do better? Manual testing is... Time consuming Error prone Irreproducible (Nearly) Impossible if we want to test a wide set of browsers and platforms YES!! Automated testing
  5. 5. Tests should be Fast to run Easy to understand Isolated Not reliant on an Internet connection
  6. 6. Benefits and pitfalls of testing Regression testing Refactoring Cross-browser testing Good documentation Helps us write cleaner interfaces (testable code) Writing good tests can be challenging
  7. 7. More information in... ● Test-Driven JavaScript Development, by Christian Johansen ● https://en.wikipedia.org/wiki/Software_testing
  8. 8. Client testing “A passing test doesn't mean no problem. It means no problem observed. This time. With these inputs. So far. On my machine.” — Michael Bolton
  9. 9. Frameworks
  10. 10. Jasmine — Scaffolding describe("A suite with setup and tear-down", function() { var foo; beforeAll(function() {}); afterAll(function() {}); beforeEach(function() { foo = 1; }); afterEach(function() { foo = 0; }); it("can contain specs with one or more expectations", function() { expect(foo).toBe(1); expect(true).toBe(true); }); });
  11. 11. Matchers expect(3).toBe(3); // Compares with === expect({a: 3}).toEqual({a: 3}); // For comparison of objects expect('barely').toMatch(/bar/); // For regular expressions expect(null).toBeDefined(); // Compares against undefined expect(undefined).toBeUndefined(); // Compares against undefined expect(null).toBeNull(); // Compares against null expect('hello').toBeTruthy(); // For boolean casting testing expect('').toBeFalsy(); // For boolean casting testing expect(['bar', 'foo']).toContain('bar'); // For finding an item in an Array expect(2).toBeLessThan(3); // For mathematical comparisons expect(3).toBeGreaterThan(2); // For mathematical comparisons expect(3.14).toBeCloseTo(3.17, 1); // For precision math comparison // For testing if a function throws an exception expect(function() { throw new Error('Error!'); }).toThrow(); // Modifier 'not' expect(false).not.toBe(true);
  12. 12. Spies describe("A suite", function() { var foo, bar = null; beforeEach(function() { foo = { setBar: function(value) { bar = value; } }; spyOn(foo, 'setBar'); foo.setBar(123); foo.setBar(456, 'another param'); }); it("that defines a spy out of the box", function() { expect(foo.setBar).toHaveBeenCalled(); // tracks that the spy was called // tracks all the arguments of its calls expect(foo.setBar).toHaveBeenCalledWith(123); expect(foo.setBar).toHaveBeenCalledWith(456, 'another param'); expect(bar).toBeNull(); // stops all execution on a function }); });
  13. 13. Spies — and.callthrough describe("A suite", function() { var foo, bar = null; beforeEach(function() { foo = { setBar: function(value) { bar = value; } }; spyOn(foo, 'setBar').and.callThrough(); foo.setBar(123); }); it("that defines a spy configured to call through", function() { expect(foo.setBar).toHaveBeenCalled(); // tracks that the spy was called expect(bar).toEqual(123); // the spied function has been called }); });
  14. 14. describe("A suite", function() { var foo, bar = null; beforeEach(function() { foo = { getBar: function() { return bar; } }; spyOn(foo, 'getBar').and.returnValue(745); }); it("that defines a spy configured to fake a return value", function() { expect(foo.getBar()).toBe(745); // when called returns the requested value expect(bar).toBeNull(); // should not affect the variable }); }); Spies — and.returnValue
  15. 15. describe("A suite", function() { var foo, bar = null; beforeEach(function() { foo = { setBar: function(value) { bar = value; } }; spyOn(foo, 'setBar').and.callFake(function() { console.log('hello'); }); foo.setBar(); // logs hello in the console. }); it("that defines a spy configured with an alternate implementation", function() { expect(foo.setBar).toHaveBeenCalled(); // tracks that the spy was called expect(bar).toBeNull(); // should not affect the variable }); }); Spies — and.callFake
  16. 16. Spies — createSpy describe("A suite", function() { var spy; beforeAll(function() { $(window).on('resize', function() { $(window).trigger('myEvent'); }); }); afterAll(function() { $(window).off('resize'); }); beforeEach(function() { spy = jasmine.createSpy(); }); it("that defines a spy created manually", function() { $(window).on('myEvent', spy); $(window).trigger('resize'); expect(spy).toHaveBeenCalled(); // tracks that the spy was called }); });
  17. 17. Spies — Other tracking properties (I) describe("A spy", function() { var foo, bar = null; beforeEach(function() { foo = { setBar: function(value) { bar = value; } }; spyOn(foo, 'setBar'); foo.setBar(123); foo.setBar(456, 'baz'); }); it("has a rich set of tracking properties", function() { expect(foo.setBar.calls.count()).toEqual(2); // tracks the number of calls // tracks the args of each call expect(foo.setBar.calls.argsFor(0)).toEqual([123]); expect(foo.setBar.calls.argsFor(1)).toEqual([456, 'baz']); // has shortcuts to the first and most recent call expect(foo.setBar.calls.first().args).toEqual([123]); expect(foo.setBar.calls.mostRecent().args).toEqual([456, 'baz']); }); });
  18. 18. Spies — Other tracking properties (II) describe("A spy", function() { var foo, bar = null; beforeEach(function() { foo = { setBar: function(value) { bar = value; } }; spyOn(foo, 'setBar'); foo.setBar(123); foo.setBar(456, 'baz'); }); it("has a rich set of tracking properties", function() { // tracks the context and return values expect(foo.setBar.calls.first().object).toEqual(foo); expect(foo.setBar.calls.first().returnValue).toBeUndefined(); // can be reset foo.setBar.calls.reset(); expect(foo.setBar.calls.count()).toBe(0); }); });
  19. 19. Asynchronous support describe("Asynchronous specs", function() { var value; beforeEach(function(done) { setTimeout(function() { value = 0; done(); }, 100); }); it("should support async execution of preparation and expectations", function(done) { expect(value).toBe(0); done(); }); });
  20. 20. Clock describe("Manually ticking the Jasmine Clock", function() { var timerCallback; beforeEach(function() { timerCallback = jasmine.createSpy(); jasmine.clock().install(); }); afterEach(function() { jasmine.clock().uninstall(); }); it("causes a timeout to be called synchronously", function() { setTimeout(timerCallback, 100); expect(timerCallback).not.toHaveBeenCalled(); jasmine.clock().tick(101); expect(timerCallback).toHaveBeenCalled(); }); });
  21. 21. Clock — Mocking the date describe("Mocking the Date object", function() { beforeEach(function() { jasmine.clock().install(); }); afterEach(function() { jasmine.clock().uninstall(); }); it("mocks the Date object and sets it to a given time", function() { var baseTime = new Date(2013, 9, 23); jasmine.clock().mockDate(baseTime); jasmine.clock().tick(50); expect(new Date().getTime()).toEqual(baseTime.getTime() + 50); }); });
  22. 22. Sinon — Spies and Stubs var spy = sinon.spy(); sinon.spy($, 'ajax'); $.ajax.restore(); sinon.stub($, 'ajax'); $.ajax.restore(); sinon.stub($, 'ajax', function(options) { console.log(options.url); }); $.ajax.restore();
  23. 23. Sinon — Fake timer describe("Manually ticking the Clock", function() { var clock, timerCallback; beforeEach(function() { timerCallback = sinon.spy(); clock = sinon.useFakeTimers(); }); afterEach(function() { clock.restore(); }); it("causes a timeout to be called synchronously", function() { setTimeout(timerCallback, 100); expect(timerCallback.callCount).toBe(0); clock.tick(101); expect(timerCallback.callCount).toBe(1); expect(new Date().getTime()).toBe(101); }); });
  24. 24. Sinon — Fake server describe("A suite with a sinon fakeServer", function() { var server; beforeEach(function() { server = sinon.fakeServer.create(); server.autoRespond = true; server.respondWith(function(xhr) { xhr.respond(200, {'Content-Type':'application/json'}, JSON.stringify({'msg': 'msg'})); }); server.xhr.useFilters = true; server.xhr.addFilter(function(method, url) { return !!url.match(/fixtures|css/); // If returns true the request will not be faked. }); }); afterEach(function() { server.restore(); }); });
  25. 25. More information in... ● https://en.wikipedia.org/wiki/List_of_unit_testing_frameworks#JavaScri pt ● http://stackoverflow.com/questions/300855/javascript-unit-test-tools- for-tdd ● http://jasmine.github.io/ ● http://sinonjs.org/
  26. 26. Test Driven Development “The best TDD can do is assure that the code does what the programmer thinks it should do. That is pretty good by the way.” — James Grenning
  27. 27. The cycle of TDD Write a test Run tests. Watch the new test fail Make the test pass Refactor to remove duplication
  28. 28. Benefits of TDD Produces code that works Honors the Single Responsibility Principle Forces conscious development Productivity boost
  29. 29. More information in... ● Test-Driven Development By Example, by Kent Beck.
  30. 30. Jasmine Disabling specs xdescribe("A disabled suite", function() { it("where the specs will not be executed", function() { expect(2).toEqual(1); }); }); describe("A suite", function() { xit("with a disabled spec declared with 'xit'", function() { expect(true).toBe(false); }); it.only("with a spec that will be executed", function() { expect(1).toBe(1); }); it("with another spec that will not be executed", function() { expect(1).toBe(1); }); });
  31. 31. Asynchronous support describe("long asynchronous specs", function() { beforeEach(function(done) { done(); }, 1000); afterEach(function(done) { done(); }, 1000); it("takes a long time", function(done) { setTimeout(done, 9000); }, 10000); });
  32. 32. Asynchronous support. Jasmine 1.3 describe("Asynchronous specs", function() { var value, flag; it("should support async execution of test preparation and expectations", function() { flag = false; value = 0; setTimeout(function() { flag = true; }, 500); waitsFor(function() { value++; return flag; }, "The Value should be incremented", 750); runs(function() { expect(value).toBeGreaterThan(0); }); }); });
  33. 33. jasmine.Clock v.1.3 it('description', function() { jasmine.Clock.useMock(); setTimeout(function() { console.log('print something'); }, 200); jasmine.Clock.tick(190); }); it('description', function() { jasmine.Clock.useMock(); jasmine.Clock.tick(190); });
  34. 34. Karma npm install karma --save-dev npm install karma-jasmine karma-chrome-launcher karma-phantomjs-launcher --save-dev npm install karma-coverage --save-dev npm install -g karma-cli Installation Configuration karma init karma.conf.js npm install grunt-karma --save-dev grunt.loadNpmTasks('grunt-karma'); karma: { unit: { configFile: 'karma.conf.js' } } Grunt task
  35. 35. Karma configuration The files array determines which files are included in the browser and which files are watched and served by Karma. Each pattern is either a simple string or an object with four properties: pattern String, no default value. The pattern to use for matching. This property is mandatory. watched Boolean (true). If autoWatch is true all files that have set watched to true will be watched for changes. included Boolean (true). Should the files be included in the browser using <script> tag? Use false if you want to load them manually, eg. using Require.js. served Boolean (true). Should the files be served by Karma's webserver?
  36. 36. THANKS FOR YOUR ATTENTION Leave your questions on the comments section

×