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. About me Andy Gale Head of Technology Content Hub Ltd

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

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

    reuse •Easier upgrade of dependencies •Continuous delivery Thursday, 16 May 13
  4. 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
  5. 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
  6. describe("some thing we need to test", function() { }); Suites:

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

    A spec is a test, just a function Thursday, 16 May 13
  8. 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
  9. 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
  10. describe("Learn about matchers", function() { it("'Sun' toBe 'Sun'", function() {

    expect("Sun").toBe("Sun"); }); }); Matchers! Thursday, 16 May 13
  11. 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
  12. 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
  13. 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
  14. it("'toBeDefined' matches against `undefined`", function() { var a = {

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

    foo: 'foo' }; expect(a.foo).not.toBeUndefined(); expect(a.bar).toBeUndefined(); }); Thursday, 16 May 13
  16. 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
  17. it("'toBeTruthy' matches boolean casted vars", function() { var a, foo

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

    = 'foo'; expect(a).toBeFalsy(); expect(foo).not.toBeFalsy(); }); Thursday, 16 May 13
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. function UKCountry(country) Returns true if country is England, Scotland, Wales

    or Northern Ireland. Our function Thursday, 16 May 13
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. Our specs catch the fact that something we rely on

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

    code •.... any other suggestions? Thursday, 16 May 13
  35. •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
  36. •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
  37. 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
  38. 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
  39. 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
  40. Make valid method pass test valid: function() { if (this.length

    < 5) { return false; } } Thursday, 16 May 13
  41. 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
  42. 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
  43. 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
  44. 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
  45. 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
  46. 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
  47. Install Node.js if not installed! $ git clone [email protected]:salgo/testing-javascript.git $

    cd testing-javascript $ sudo npm install -g grunt-cli $ npm install Example Setup Thursday, 16 May 13
  48. $ 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