Slide 1

Slide 1 text

Testing Javascript Andy Gale Thursday, 16 May 13

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

describe("some thing we need to test", function() { }); Suites: describe Your suite is a collection of tests, just a function Thursday, 16 May 13

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

Thursday, 16 May 13

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

Thursday, 16 May 13

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

Thursday, 16 May 13

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

Confirm our specs pass Thursday, 16 May 13

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

Confirm our specs fail Thursday, 16 May 13

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

Confirm our specs pass Thursday, 16 May 13

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

Confirm our new specs fail Thursday, 16 May 13

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

Confirm our specs pass Thursday, 16 May 13

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

•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

Slide 49

Slide 49 text

+ Example Thursday, 16 May 13

Slide 50

Slide 50 text

•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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

Check our spec fails Thursday, 16 May 13

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

Confirm our spec now passes Thursday, 16 May 13

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

Confirm our new spec now passes Thursday, 16 May 13

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

Check our new 3 specs fail Thursday, 16 May 13

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

Confirm our specs pass Thursday, 16 May 13

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

$ 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

Slide 69

Slide 69 text

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