Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Testing Javascript

Testing Javascript

Introduction to testing Javascript with Jasmine. Backbone.js and Grunt also covered. Given at Southville JS.

https://github.com/salgo/testing-javascript

Andy Gale

May 16, 2013
Tweet

More Decks by Andy Gale

Other Decks in Programming

Transcript

  1. Testing Javascript Andy Gale Thursday, 16 May 13

  2. About me Andy Gale Head of Technology Content Hub Ltd

    @andygale andy-gale.com Thursday, 16 May 13
  3. What we’ll cover •Benefits of testing •Jasmine introduction •Test Driven

    Development with Jasmine •Grunt Thursday, 16 May 13
  4. Benefits of testing •Code stability •Tested code is easier to

    reuse •Easier upgrade of dependencies •Continuous delivery Thursday, 16 May 13
  5. http://pivotal.github.io/jasmine/ Jasmine is a behavior-driven development framework for testing JavaScript

    code. It does not depend on any other JavaScript frameworks. It does not require a DOM. And it has a clean, obvious syntax so that you can easily write tests. Thursday, 16 May 13
  6. describe("some thing we need to test", function() { it("contains spec

    with an expectation", function() { expect(true).toBe(true); }); }); Looks like this Thursday, 16 May 13
  7. describe("some thing we need to test", function() { }); Suites:

    describe Your suite is a collection of tests, just a function Thursday, 16 May 13
  8. it("contains spec with an expectation", function() { }); Specs: it

    A spec is a test, just a function Thursday, 16 May 13
  9. Expectations: expect Allows us to specify the variable which we

    wish to match Matchers: toBe Allows us to specify the value we wish to match expect(true).toBe(true); Thursday, 16 May 13
  10. describe("some thing we need to test", function() { it("contains spec

    with an expectation", function() { expect(true).toBe(true); }); }); Our first spec Thursday, 16 May 13
  11. Thursday, 16 May 13

  12. describe("Learn about matchers", function() { it("'Sun' toBe 'Sun'", function() {

    expect("Sun").toBe("Sun"); }); }); Matchers! Thursday, 16 May 13
  13. it("a toEqual 5", function() { var a = 5; expect(a).toEqual(5);

    }); Thursday, 16 May 13
  14. it("'Sun' toEqual 'Sun'", function() { expect("Sun").toEqual("Sun"); }); it("a toEqual 5",

    function() { var a = 5; expect(a).toEqual(5); }); toEqual() works for simple literals and variables Thursday, 16 May 13
  15. Thursday, 16 May 13

  16. it("toEqual works for objects", function() { var foo = {

    a: 12, b: 34 }; var bar = { a: 12, b: 34 }; expect(foo).toEqual(bar); }); toEqual() works for object Thursday, 16 May 13
  17. Thursday, 16 May 13

  18. it("The 'toMatch' for regular expressions", function() { var message =

    'foo bar baz'; expect(message).toMatch(/bar/); expect(message).toMatch('bar'); expect(message).not.toMatch(/quux/); }); Thursday, 16 May 13
  19. it("'toBeDefined' matches against `undefined`", function() { var a = {

    foo: 'foo' }; expect(a.foo).toBeDefined(); expect(a.bar).not.toBeDefined(); }); Thursday, 16 May 13
  20. it("`toBeUndefined` matches against `undefined`", function() { var a = {

    foo: 'foo' }; expect(a.foo).not.toBeUndefined(); expect(a.bar).toBeUndefined(); }); Thursday, 16 May 13
  21. it("'toBeNull' matches against null", function() { var a = null;

    var foo = 'foo'; expect(null).toBeNull(); expect(a).toBeNull(); expect(foo).not.toBeNull(); }); Thursday, 16 May 13
  22. it("'toBeTruthy' matches boolean casted vars", function() { var a, foo

    = 'foo'; expect(foo).toBeTruthy(); expect(a).not.toBeTruthy(); }); Thursday, 16 May 13
  23. it("'toBeFalsy' matches boolean casted vars", function() { var a, foo

    = 'foo'; expect(a).toBeFalsy(); expect(foo).not.toBeFalsy(); }); Thursday, 16 May 13
  24. it("'toContain' matches an item in an Array", function() { var

    a = ['foo', 'bar', 'baz']; expect(a).toContain('bar'); expect(a).not.toContain('quux'); }); Thursday, 16 May 13
  25. it("'toBeLessThan' for maths", function() { var pi = 3.1415926, e

    = 2.78; expect(e).toBeLessThan(pi); expect(pi).not.toBeLessThan(e); }); Thursday, 16 May 13
  26. it("'toBeGreaterThan' is for maths", function() { var pi = 3.1415926,

    e = 2.78; expect(pi).toBeGreaterThan(e); expect(e).not.toBeGreaterThan(pi); }); Thursday, 16 May 13
  27. it("'toBeCloseTo' for precision math comparison", function() { var pi =

    3.1415926, e = 2.78; expect(pi).not.toBeCloseTo(e, 2); expect(pi).toBeCloseTo(e, 0); }); Thursday, 16 May 13
  28. it("The 'toThrow' matcher is for testing if a function throws

    an exception", function() { var foo = function() { return 1 + 2; }; var bar = function() { return a + 1; }; expect(foo).not.toThrow(); expect(bar).toThrow(); }); Thursday, 16 May 13
  29. Confirm our specs pass Thursday, 16 May 13

  30. Let’s write a function using Test Driven Development Thursday, 16

    May 13
  31. Test Driven Development •Work out what code should do •Write

    test to prove code you haven’t written fails •Write code •Run test to prove code works Thursday, 16 May 13
  32. function UKCountry(country) Returns true if country is England, Scotland, Wales

    or Northern Ireland. Our function Thursday, 16 May 13
  33. function UKCountry(country) { return false; } Although we write the

    specs first, a skeleton function needs to exist for the tests to run without error Thursday, 16 May 13
  34. describe("Countries in the United Kingdom", function() { it("England", function() {

    expect(UKCountry('England')).toBe(true); }); it("Scotland", function() { expect(UKCountry('Scotland')).toBe(true); }); it("Wales", function() { expect(UKCountry('Wales')).toBe(true); }); it("Northern Ireland", function() { expect(UKCountry('Northern Ireland')).toBe(true); }); }); Our spec Thursday, 16 May 13
  35. Confirm our specs fail Thursday, 16 May 13

  36. function UKCountry(country) { if (country === 'England') { return true;

    } else if (country === 'Scotland') { return true; } else if (country === 'Wales') { return true; } else if (country === 'Northern Ireland') { return true; } } Write our function Thursday, 16 May 13
  37. Confirm our specs pass Thursday, 16 May 13

  38. All good? All our tests pass let’s go to the

    pub! Thursday, 16 May 13
  39. No! There’s a problem! •Test what shouldn’t happen as well

    as what should! •Don’t forget that tests are code too. Your tests could pass, you could have a bug in your tests!!! Thursday, 16 May 13
  40. describe("Countries outside United Kingdom", function() { it("Ireland", function() { expect(UKCountry('Ireland')).toBe(false);

    }); it("Spain", function() { expect(UKCountry('Spain')).toBe(false); }); }); We need another suite! Thursday, 16 May 13
  41. Confirm our new specs fail Thursday, 16 May 13

  42. function UKCountry(country) { if (country === 'England') { return true;

    } else if (country === 'Scotland') { return true; } else if (country === 'Wales') { return true; } else if (country === 'Northern Ireland') { return true; } else { return false; } } Fix the bug Thursday, 16 May 13
  43. Confirm our specs pass Thursday, 16 May 13

  44. Let’s pretend •That our UKCountry function is actually part of

    a library we use •We update to the latest version of that library and... Thursday, 16 May 13
  45. Image by ScottishGovernment at http://www.flickr.com/photos/scottishgovernment/6762166885/ Scotland votes yes! Thursday, 16

    May 13
  46. Our specs catch the fact that something we rely on

    has changed Thursday, 16 May 13
  47. Testing traditional Javascript •Separate DOM and jQuery code from logic

    code •.... any other suggestions? Thursday, 16 May 13
  48. •Allows us to apply sense and structure to our code

    •Small, well defined methods •That helps us test our Javascript Let’s try using Backbone.js Thursday, 16 May 13
  49. + Example Thursday, 16 May 13

  50. •Our Zoo collection of Animal models must contain at least

    5 animals and one must be of the type "monkey" •The Zoo collection must not contain any Animal models of type "snake" because the local residents are worried about being bitten by a snake since the incident Bristol Zoo Thursday, 16 May 13
  51. Write skeleton models first Animal = Backbone.Model.extend({}); Zoo = Backbone.Collection.extend({

    model: Animal, initialize: function() { _.bindAll(this, 'valid'); }, valid: function() { } }); Thursday, 16 May 13
  52. Define fixtures in JSON var jsonAnimals = {'Hippo': { id:

    1, name: 'Steve', type: 'hippo' }, 'Snake': { id: 2, name: 'Dave', type: 'snake' }, 'Bear': { id: 3, name: 'Susan', type: 'bear' }, 'Panda': { id: 4, name: 'Lee', type: 'panda' }, 'Lion': { id: 5, name: 'Jim', type: 'lion' }, 'Monkey': { id: 6, name: 'Carla', type: 'monkey' }, 'Frog': { id: 7, name: 'Lauren', type: 'frog' }, 'Penguin': { id: 8, name: 'Kate', type: 'penguin' } }; Thursday, 16 May 13
  53. First spec ensures an empty zoo is invalid describe("A Zoo",

    function() { it('which is empty is not valid', function() { var zoo = new Zoo(); expect(zoo.valid()).toBe(false); }); }); Thursday, 16 May 13
  54. Check our spec fails Thursday, 16 May 13

  55. Make valid method pass test valid: function() { if (this.length

    < 5) { return false; } } Thursday, 16 May 13
  56. Confirm our spec now passes Thursday, 16 May 13

  57. Spec to check a zoo with 4 animals is invalid

    it('which has four animals is not valid', function() { var zoo = new Zoo([ jsonAnimals.Hippo, jsonAnimals.Bear, jsonAnimals.Panda, jsonAnimals.Lion, ]); expect(zoo.valid()).toBe(false); }); Thursday, 16 May 13
  58. Confirm our new spec now passes Thursday, 16 May 13

  59. Zoo which has five animals, no monkey and no snake,

    is not valid it('which has five animals, no monkey and no snake, is not valid', function() { var zoo = new Zoo([ jsonAnimals.Hippo, jsonAnimals.Bear, jsonAnimals.Panda, jsonAnimals.Lion, jsonAnimals.Frog ]); expect(zoo.valid()).toBe(false); }); Thursday, 16 May 13
  60. Zoo which has five animals, a monkey and no snake,

    is valid it('which has five animals, a monkey and no snake, is valid', function() { var zoo = new Zoo([ jsonAnimals.Hippo, jsonAnimals.Bear, jsonAnimals.Panda, jsonAnimals.Lion, jsonAnimals.Monkey ]); expect(zoo.valid()).toBe(true); }); Thursday, 16 May 13
  61. Zoo which has five animals, a monkey and a snake,

    is not valid it('which has five animals, a monkey and a snake, is not valid', function() { var zoo = new Zoo([ jsonAnimals.Hippo, jsonAnimals.Snake, jsonAnimals.Panda, jsonAnimals.Lion, jsonAnimals.Monkey ]); expect(zoo.valid()).toBe(false); }); Thursday, 16 May 13
  62. Check our new 3 specs fail Thursday, 16 May 13

  63. Make valid method pass test valid: function() { if (this.length

    < 5) { return false; } var i = 0, snake = 0, monkey = 0; for (; i < this.length; i++) { var m = this.at(i), type = m.get('type'); if (type === 'snake') { snake++; } else if (type === 'monkey') { monkey++; } } if (snake > 0) { return false; } else if (monkey > 0) { return true; } else { return false; } } Thursday, 16 May 13
  64. Confirm our specs pass Thursday, 16 May 13

  65. Testing Backbone.js views http://addyosmani.github.io/backbone-fundamentals/#views-2 More information in this book or

    at the URL below. Thursday, 16 May 13
  66. Finally, let’s use Grunt to run our Jasmine tests from

    the command line so we can use them as part of a standard test script http://gruntjs.com/ Thursday, 16 May 13
  67. Install Node.js if not installed! $ git clone git@github.com:salgo/testing-javascript.git $

    cd testing-javascript $ sudo npm install -g grunt-cli $ npm install Example Setup Thursday, 16 May 13
  68. $ grunt Running "jasmine:src" (jasmine) task Testing jasmine specs via

    phantom ............ 12 specs in 0.005s. >> 0 failures Done, without errors. Example Setup Thursday, 16 May 13
  69. Questions?? Andy Gale Head of Technology Content Hub Ltd @andygale

    andy-gale.com Thursday, 16 May 13