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

    View full-size slide

  2. About me
    Andy Gale
    Head of Technology
    Content Hub Ltd
    @andygale
    andy-gale.com
    Thursday, 16 May 13

    View full-size slide

  3. What we’ll cover
    •Benefits of testing
    •Jasmine introduction
    •Test Driven Development with Jasmine
    •Grunt
    Thursday, 16 May 13

    View full-size slide

  4. Benefits of testing
    •Code stability
    •Tested code is easier to reuse
    •Easier upgrade of dependencies
    •Continuous delivery
    Thursday, 16 May 13

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  8. it("contains spec with an expectation", function() {
    });
    Specs: it
    A spec is a test, just a function
    Thursday, 16 May 13

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  11. Thursday, 16 May 13

    View full-size slide

  12. describe("Learn about matchers", function() {
    it("'Sun' toBe 'Sun'", function() {
    expect("Sun").toBe("Sun");
    });
    });
    Matchers!
    Thursday, 16 May 13

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

  15. Thursday, 16 May 13

    View full-size slide

  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

    View full-size slide

  17. Thursday, 16 May 13

    View full-size slide

  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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  29. Confirm our specs pass
    Thursday, 16 May 13

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

  32. function UKCountry(country)
    Returns true if country is
    England, Scotland, Wales or
    Northern Ireland.
    Our function
    Thursday, 16 May 13

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  35. Confirm our specs fail
    Thursday, 16 May 13

    View full-size slide

  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

    View full-size slide

  37. Confirm our specs pass
    Thursday, 16 May 13

    View full-size slide

  38. All good?
    All our tests pass let’s go to the pub!
    Thursday, 16 May 13

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  41. Confirm our new specs fail
    Thursday, 16 May 13

    View full-size slide

  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

    View full-size slide

  43. Confirm our specs pass
    Thursday, 16 May 13

    View full-size slide

  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

    View full-size slide

  45. Image by ScottishGovernment at http://www.flickr.com/photos/scottishgovernment/6762166885/
    Scotland votes yes!
    Thursday, 16 May 13

    View full-size slide

  46. Our specs catch the fact that something
    we rely on has changed
    Thursday, 16 May 13

    View full-size slide

  47. Testing traditional Javascript
    •Separate DOM and jQuery code from
    logic code
    •.... any other suggestions?
    Thursday, 16 May 13

    View full-size slide

  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

    View full-size slide

  49. +
    Example
    Thursday, 16 May 13

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  54. Check our spec fails
    Thursday, 16 May 13

    View full-size slide

  55. Make valid method pass test
    valid: function() {
    if (this.length < 5) {
    return false;
    }
    }
    Thursday, 16 May 13

    View full-size slide

  56. Confirm our spec now passes
    Thursday, 16 May 13

    View full-size slide

  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

    View full-size slide

  58. Confirm our new spec now passes
    Thursday, 16 May 13

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  62. Check our new 3 specs fail
    Thursday, 16 May 13

    View full-size slide

  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

    View full-size slide

  64. Confirm our specs pass
    Thursday, 16 May 13

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  67. 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

    View full-size slide

  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

    View full-size slide

  69. Questions??
    Andy Gale
    Head of Technology
    Content Hub Ltd
    @andygale
    andy-gale.com
    Thursday, 16 May 13

    View full-size slide