testing javascriptwithjasmine sydjs

38
Testing JavaScript LIKE A BOSS Jo Cranford @jocranford Thursday, 19 July 12

Upload: jo-cranford

Post on 06-May-2015

878 views

Category:

Technology


0 download

DESCRIPTION

Slides from my presentation on July 18 at Syd JS

TRANSCRIPT

Page 1: Testing javascriptwithjasmine sydjs

Testing JavaScriptLIKE A BOSS

Jo Cranford@jocranford

Thursday, 19 July 12

Page 2: Testing javascriptwithjasmine sydjs

Y U NO JAVASCRIPT TESTS?

Thursday, 19 July 12

Page 3: Testing javascriptwithjasmine sydjs

BDD With Jasmine Is Awesome Sauce

describe("Score Calculation Behaviour", function() {

it("should score 0 when no pins are knocked down", function() {

var game = new BowlingGame(10); game.roll(0);

expect(game.score()).toBe(0);

}); });

Thursday, 19 July 12

Page 4: Testing javascriptwithjasmine sydjs

Is JavaScript Ever Really That Simple?

Thursday, 19 July 12

Page 5: Testing javascriptwithjasmine sydjs

What About ...

• Asynchronous goodness

• Interacting with teh DOMz

• Evil Legacy Code

• Continuous Integration

• Clean readable tests that reflect your domain

Thursday, 19 July 12

Page 6: Testing javascriptwithjasmine sydjs

Approaches To Testing Asynchronous Code

Thursday, 19 July 12

Page 7: Testing javascriptwithjasmine sydjs

Let’s Load Some JSON

[ { "firstName": "Jo", "lastName": "Cranford", "company": "Atlassian" }, { "firstName": "Rachel", "lastName": "Laycock", "company": "ThoughtWorks" }]

Thursday, 19 July 12

Page 8: Testing javascriptwithjasmine sydjs

The JavaScript Code

var Presentation = function() { this.presenters = [];};

Presentation.prototype.loadPresenters = function() { var presenters = this.presenters;

$.getJSON("people.json", function(data) { $.each(data, function(idx, person) { presenters.push(person); }); });};

Thursday, 19 July 12

Page 9: Testing javascriptwithjasmine sydjs

Easy, Right?

describe("How not to test an asynchronous function", function () {

it("should load the presenters", function () {

var presentation = new Presentation(); presentation.loadPresenters();

expect(presentation.presenters.length).toBe(2);

});});

Thursday, 19 July 12

Page 10: Testing javascriptwithjasmine sydjs

Well ... Not So Much.

Thursday, 19 July 12

Page 11: Testing javascriptwithjasmine sydjs

But This Might Work ...

describe("Still not ideal though", function () {

it("should load the presenters", function () {

spyOn($, "getJSON").andCallFake(function (url, callback) { callback([{},{}]); })

var presentation = new Presentation(); presentation.loadPresenters();

expect(presentation.presenters.length).toBe(2);

});});

Thursday, 19 July 12

Page 12: Testing javascriptwithjasmine sydjs

A Little Detour ...

Thursday, 19 July 12

Page 13: Testing javascriptwithjasmine sydjs

Spy On An Existing Method

it("can spy on an existing method", function() {

var fakeElement = $("<div style='display:none'></div>"); spyOn(fakeElement, 'show');

var toggleable = new Toggleable(fakeElement);

toggleable.toggle();

expect(fakeElement.show).toHaveBeenCalled();

});

Thursday, 19 July 12

Page 14: Testing javascriptwithjasmine sydjs

Spy On An Existing Method

it("can create a method for you", function() {

var fakeElement = {}; fakeElement.css = function() {}; fakeElement.show = jasmine.createSpy("Show spy");

var toggleable = new Toggleable(fakeElement);

toggleable.toggle();

expect(fakeElement.show).toHaveBeenCalled();

});

Thursday, 19 July 12

Page 15: Testing javascriptwithjasmine sydjs

Wait, There’s More ...

• expect(spy).not.toHaveBeenCalled()

• createSpy().andReturn(something)

• createSpy().andCallFake(function() {})

• createSpy().andCallThrough()

Thursday, 19 July 12

Page 16: Testing javascriptwithjasmine sydjs

Spy On The Details

• expect(spy).toHaveBeenCalled()

• expect(spy.callCount).toBe(x)

• expect(spy).toHaveBeenCalledWith()

• Tip: use jasmine.any(Function/Object) for parameters you don’t care about

Thursday, 19 July 12

Page 17: Testing javascriptwithjasmine sydjs

... And We’re Back.

Sooooo ... spies are great and all, but what if your callback function

takes a while to run?

Thursday, 19 July 12

Page 18: Testing javascriptwithjasmine sydjs

Don’t Do This At Home.

Presentation.prototype.loadPresentersMoreSlowly = function() {

var preso = this;

$.getJSON("people.json", function(data) { setTimeout(function() { $.each(data, function(idx, person) { preso.presenters.push(person); }); }, 2000); });

};

Thursday, 19 July 12

Page 19: Testing javascriptwithjasmine sydjs

Don’t Do This, Either.it("should have loaded after three seconds, right?", function() {

spyOn($, "getJSON").andCallFake(function(url, callback) { callback([{}, {}]); })

var presentation = new Presentation(); presentation.loadPresentersMoreSlowly();

setTimeout(function() { expect(presentation.presenters.length).toBe(2); }, 3000);

});

Thursday, 19 July 12

Page 20: Testing javascriptwithjasmine sydjs

But What If I Just ...

Presentation.prototype.loadPresentersMoreSlowly = function() {

var preso = this;

$.getJSON("people.json", function(data) { setTimeout(function() { $.each(data, function(idx, person) { preso.presenters.push(person); }); preso.presentersHaveLoaded = true; }, 2000); });

};

Thursday, 19 July 12

Page 21: Testing javascriptwithjasmine sydjs

Now Wait, Wait ... RUN!it("should load the presenters", function() {

spyOn($, "getJSON").andCallFake(function(url, callback) { callback([{}, {}]); })

var presentation = new Presentation(); presentation.loadPresentersMoreSlowly();

waitsFor(function() { return presentation.presentersHaveLoaded; }, "presenters have loaded");

runs(function() { expect(presentation.presenters.length).toBe(2); });

});

Thursday, 19 July 12

Page 22: Testing javascriptwithjasmine sydjs

Testing Interaction With The DOM

• Do you REALLY need to?

• Tests will have a high maintenance cost

• Instead separate logic from view and test logic

• Use templates for the view

Thursday, 19 July 12

Page 23: Testing javascriptwithjasmine sydjs

Testing Interaction With The DOM

it("should display the score", function() {

setFixtures("<div id='score'></div>");

var bowlingGameView = new BowlingGameView(); bowlingGameView.showScore(100);

expect($("#score").text()).toBe("Your current score is 100");

});

https://github.com/velesin/jasmine-jqueryThursday, 19 July 12

Page 24: Testing javascriptwithjasmine sydjs

Legacy (untested) JavaScript Code

• Long methods

• Violation of Single Responsibility Principle

• Side effects

• Lack of dependency injection

• Lots of new X()

• Unclear intentions

Thursday, 19 July 12

Page 25: Testing javascriptwithjasmine sydjs

Testing Interaction

it("should call the method on the dependency", function() {

var dependency = {}; dependency.method = jasmine.createSpy();

var myObject = new Something(dependency); myObject.doSomething();

expect(dependency.method).toHaveBeenCalled();});

Thursday, 19 July 12

Page 26: Testing javascriptwithjasmine sydjs

If Dependencies Aren’t Injected ...

var LegacySomething = function() {

this.doSomething = function() { var dependency = new Dependency(); dependency.method(); };

};

Thursday, 19 July 12

Page 27: Testing javascriptwithjasmine sydjs

Create Stubs

it("is a pain but not impossible", function() {

Dependency = function() {}; Dependency.prototype.method = jasmine.createSpy()

var myObject = new LegacySomething(); myObject.doSomething();

expect(Dependency.prototype.method).toHaveBeenCalled();});

Thursday, 19 July 12

Page 28: Testing javascriptwithjasmine sydjs

Continuous Integration

• Ruby Gem

• Maven

• Node.js

• Rhino (Java)

Thursday, 19 July 12

Page 29: Testing javascriptwithjasmine sydjs

Ruby Gem

> rake jasmine:ci

require 'jasmine'load 'jasmine/tasks/jasmine.rake'

https://github.com/pivotal/jasmine-gemThursday, 19 July 12

Page 30: Testing javascriptwithjasmine sydjs

Maven

> mvn clean test

http://searls.github.com/jasmine-maven-plugin/Thursday, 19 July 12

Page 31: Testing javascriptwithjasmine sydjs

Node.js

> jasmine-node specs/

https://github.com/mhevery/jasmine-nodeThursday, 19 July 12

Page 32: Testing javascriptwithjasmine sydjs

Rhino

• Download:

• Rhino (js.jar) from Mozilla

• env.rhino.js from www.envjs.com

• Jasmine console reporter from Larry Myers Jasmine Reporters project (github)

http://www.build-doctor.com/2010/12/08/javascript-bdd-jasmine/Thursday, 19 July 12

Page 33: Testing javascriptwithjasmine sydjs

Rhinoload('env.rhino.1.2.js');

Envjs.scriptTypes['text/javascript'] = true;

var specFile;

for (i = 0; i < arguments.length; i++) { specFile = arguments[i];

console.log("Loading: " + specFile);

window.location = specFile}

> java -jar js.jar -opt -1 env.bootstrap.js ../SpecRunner.html

Thursday, 19 July 12

Page 34: Testing javascriptwithjasmine sydjs

Extending Jasmine With Custom Matchers

it("should match the latitude and longitude", function() {

var pointOnMap = { latitude: "51.23", longitude: "-10.14" };

expect(pointOnMap.latitude).toBe("51.23"); expect(pointOnMap.longitude).toBe("-10.14");

});

it("should match the latitude and longitude", function() {

var pointOnMap = { latitude: "51.23", longitude: "-10.14" };

expect(pointOnMap).toHaveLatitude("51.23"); expect(pointOnMap).toHaveLongitude("-10.14");

});

Thursday, 19 July 12

Page 35: Testing javascriptwithjasmine sydjs

Extending Jasmine With Custom Matchers

it("should match the latitude and longitude", function() {

var pointOnMap = { latitude: "51.23", longitude: "-10.14" };

expect(pointOnMap).toHaveLatLongCoordinates("51.23", "-10.14");

});

Thursday, 19 July 12

Page 36: Testing javascriptwithjasmine sydjs

Extending Jasmine With Custom Matchers

beforeEach(function() { this.addMatchers({ toHaveLatitude: function(lat) { return this.actual.latitude === lat; }, toHaveLongitude: function(lat) { return this.actual.latitude === lat; }, toHaveLatLongCoordinates: function(lat, lng) { return (this.actual.latitude === lat &&

this.actual.longitude === lng); } });});

Thursday, 19 July 12

Page 37: Testing javascriptwithjasmine sydjs

Custom Failure Messages

toHaveLatitude: function(lat) { this.message = function() { return "Expected Latitude " + this.actual.latitude

+ " to be " + lat; }; return this.actual.latitude === lat;}

Thursday, 19 July 12

Page 38: Testing javascriptwithjasmine sydjs

Thursday, 19 July 12