JavaScript unit testing with
@rsim github.com/rsim Raimonds Simanovskis
The easiest Business Intelligence tool on the Web
Using threads in Ruby applications
Using threads in JavaScript applications
You can’t :)
JavaScript unit testing with
RSpec-like testing for JavaScript
Syntax describe("A suite", function() { it("contains spec with an expectation", function() { expect(true).toBe(true); }); }); describe("A suite is just a function", function() { var a; it("and so is a spec", function() { a = true; expect(a).toBe(true); }); });
Matchers describe("The 'toBe' matcher compares with ===", function() { it("and has a positive case ", function() { expect(true).toBe(true); }); it("and can have a negative case", function() { expect(false).not.toBe(true); }); });
Included matchers toBe toContain toEqual toBeLessThan toMatch toBeGreaterThan toBeDefined toBeCloseTo toBeUndefined toThrow toBeNull toBeTruthy toBeFalsy
Setup & teardown describe("A spec (with setup and tear-down)", function() { var foo; beforeEach(function() { foo = 0; foo += 1; }); afterEach(function() { foo = 0; }); it("is just a function, so it can contain any code", function() { expect(foo).toEqual(1); }); it("can have more than one expectation", function() { expect(foo).toEqual(1); expect(true).toEqual(true); }); });
describe("A spec", function() { var foo; beforeEach(function() { Nesting foo = 0; foo += 1; }); afterEach(function() { foo = 0; }); it("is just a function, so it can contain any code", function() { expect(foo).toEqual(1); }); it("can have more than one expectation", function() { expect(foo).toEqual(1); expect(true).toEqual(true); }); describe("nested inside a second describe", function() { var bar; beforeEach(function() { bar = 1; }); it("can reference both scopes as needed ", function() { expect(foo).toEqual(bar); }); }); });
Disabling xdescribe("A spec", function() { var foo; beforeEach(function() { foo = 0; foo += 1; }); xit("is just a function, so it can contain any code", function() { expect(foo).toEqual(1); }); });
describe("A spy", function() { Spies var foo, bar = null; beforeEach(function() { foo = { setBar: function(value) { bar = value; } }; spyOn(foo, 'setBar'); foo.setBar(123); foo.setBar(456, 'another param'); }); it("tracks that the spy was called", function() { expect(foo.setBar).toHaveBeenCalled(); }); it("tracks its number of calls", function() { expect(foo.setBar.calls.length).toEqual(2); }); it("tracks all the arguments of its calls", function() { expect(foo.setBar).toHaveBeenCalledWith(123); expect(foo.setBar).toHaveBeenCalledWith(456, 'another param'); }); it("allows access to the most recent call", function() { expect(foo.setBar.mostRecentCall.args[0]).toEqual(456); }); it("allows access to other calls", function() { expect(foo.setBar.calls[0].args[0]).toEqual(123); }); it("stops all execution on a function", function() { expect(bar).toBeNull(); }); });
describe("A spy, when faking a return value", function() { var foo, bar, fetchedBar; beforeEach(function() { andReturn foo = { setBar: function(value) { bar = value; }, getBar: function() { return bar; } }; spyOn(foo, 'getBar').andReturn(745); foo.setBar(123); fetchedBar = foo.getBar(); }); it("tracks that the spy was called", function() { expect(foo.getBar).toHaveBeenCalled(); }); it("should not effect other functions", function() { expect(bar).toEqual(123); }); it("when called returns the requested value", function() { expect(fetchedBar).toEqual(745); }); });
Async tests describe("Asynchronous specs", function() { var value, flag; it("should support async execution of test preparation and exepectations", function() { runs(function() { flag = false; value = 0; setTimeout(function() { flag = true; }, 500); }); waitsFor(function() { value++; return flag; }, "The Value should be incremented", 750); runs(function() { expect(value).toBeGreaterThan(0); }); }); });
Jasmine tests runner Demo
Works nice with CoffeeScript and Backbone.js
Testing Backbone.js model describe "Todo", -> todo = null ajaxCall = (param) -> jQuery.ajax.mostRecentCall.args[0][param] beforeEach -> todo = new TodoApp.Todo todos = new TodoApp.TodoList [todo] it "should initialize with empty content", -> expect(todo.get "content").toEqual "empty todo..." it "should initialize as not done", -> expect(todo.get "done").toBeFalsy() it "should save after toggle", -> spyOn jQuery, "ajax" todo.toggle() expect(ajaxCall "url").toEqual "/todos" expect(todo.get "done").toBeTruthy()
and collection describe "TodoList", -> attributes = [ content: "First" done: true , content: "Second" ] todos = null beforeEach -> todos = new TodoApp.TodoList attributes it "should return done todos", -> expect(_.invoke todos.done(), "toJSON").toEqual [attributes[0]] it "should return remaining todos", -> expect(_.invoke todos.remaining(), "toJSON").toEqual [attributes[1]]
jasmine-jquery • a set of custom matchers for jQuery framework • an API for handling HTML, CSS, and JSON fixtures in your specs
expect($('<div id="some-id"></div>')).toBe('div#some-id') expect($('<input type="checkbox" checked="checked"/>')).toBeChecked() expect($('<div style="display: none; margin: 10px;"></ div>')).toHaveCss({display: "none", margin: "10px"}) expect($('<option selected="selected"></option>')).toBeSelected() expect($('<div><span class="some-class"></span></ div>')).toContain('span.some-class') expect($('<div class="some-class"></div>')).toHaveClass("some-class") expect($('<div><span></span></div>')).toHaveHtml('<span></span>') expect($('<div><ul></ul><h1>header</h1></div>')).toContainHtml('<ul></ ul>') expect($('<div>some text</div>')).toHaveText('some text') expect($('<input type="text" value="some text"/>')).toHaveValue('some text') expect('<input type='submit' disabled='disabled'/>').toBeDisabled() expect($('<input type='text' />').focus()).toBeFocused()
HTML fixtures In myfixture.html file: <div id="my-fixture">some complex content here</div> Inside your test: loadFixtures('myfixture.html'); $('#my-fixture').myTestedPlugin(); expect($('#my-fixture')).to...;
jasmine-fixture // Let's say you want to write a Jasmine spec for some code // that needs to select elements from the DOM with jQuery: $('#toddler .hidden.toy input[name="toyName"][value="cuddle bunny"]') // In the good ol' days of manually crafting your HTML fixtures, // you'd have to append some raw HTML to the DOM like this: beforeEach(function(){ $('<div id="toddler"><div class="hidden toy"><input name="toyName" value="cuddle bunny"></div></div>').appendTo('body'); }); afterEach(function(){ $('#toddler').remove() }); // But jasmine-fixture's affix method lets you do this instead: beforeEach(function(){ affix('#toddler .hidden.toy input[name="toyName"][value="cuddle bunny"]') });
jasmine-given describe("assigning stuff to this", function() { Given(function() { this.number = 24; }); Given(function() { this.number++; }); When(function() { this.number *= 2; }); Then(function() { return this.number === 50; }); // or Then(function() { expect(this.number).toBe(50) }); }); describe("assigning stuff to variables", function() { var subject; Given(function() { subject = []; }); When(function() { subject.push('foo'); }); Then(function() { return subject.length === 1; }); // or Then(function() { expect(subject.length).toBe(1); }); });
jasmine-given describe "assigning stuff to this", -> Given -> @number = 24 Given -> @number++ When -> @number *= 2 Then -> @number == 50 # or Then -> expect(@number).toBe(50) describe "assigning stuff to variables", -> subject=null Given -> subject = [] When -> subject.push('foo') Then -> subject.length == 1 # or Then -> expect(subject.length).toBe(1)
jasmine-stealth describe("multiple stubbings", function() { var someSpy; beforeEach(function() { someSpy = jasmine.createSpy(); someSpy.when("pirate", { booty: ["jewels",jasmine.any(String)]}).thenReturn("argh!"); someSpy.when("panda",1).thenReturn("sad"); }); it("stubs the first accurately", function() { expect(someSpy("pirate",{ booty: ["jewels","coins"]})).toBe("argh!"); }); it("stubs the second too", function() { expect(someSpy("panda",1)).toBe("sad"); }); it("doesn't return anything when a stubbing isn't satisfied",function(){ expect(someSpy("anything else at all")).not.toBeDefined(); }); });
References http://pivotal.github.io/jasmine/ http://searls.testdouble.com/posts/2013-03-21- jasmine-tactics-screencast.html https://github.com/rsim/backbone_coffeescript_demo https://github.com/rsim/rpn_calculator http://vimeo.com/53655205

JavaScript Unit Testing with Jasmine

  • 1.
  • 2.
    @rsim github.com/rsim Raimonds Simanovskis
  • 3.
    The easiest BusinessIntelligence tool on the Web
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
    Syntax describe("A suite", function(){ it("contains spec with an expectation", function() { expect(true).toBe(true); }); }); describe("A suite is just a function", function() { var a; it("and so is a spec", function() { a = true; expect(a).toBe(true); }); });
  • 10.
    Matchers describe("The 'toBe' matchercompares with ===", function() { it("and has a positive case ", function() { expect(true).toBe(true); }); it("and can have a negative case", function() { expect(false).not.toBe(true); }); });
  • 11.
    Included matchers toBe toContain toEqual toBeLessThan toMatch toBeGreaterThan toBeDefined toBeCloseTo toBeUndefined toThrow toBeNull toBeTruthy toBeFalsy
  • 12.
    Setup & teardown describe("Aspec (with setup and tear-down)", function() { var foo; beforeEach(function() { foo = 0; foo += 1; }); afterEach(function() { foo = 0; }); it("is just a function, so it can contain any code", function() { expect(foo).toEqual(1); }); it("can have more than one expectation", function() { expect(foo).toEqual(1); expect(true).toEqual(true); }); });
  • 13.
    describe("A spec", function(){ var foo; beforeEach(function() { Nesting foo = 0; foo += 1; }); afterEach(function() { foo = 0; }); it("is just a function, so it can contain any code", function() { expect(foo).toEqual(1); }); it("can have more than one expectation", function() { expect(foo).toEqual(1); expect(true).toEqual(true); }); describe("nested inside a second describe", function() { var bar; beforeEach(function() { bar = 1; }); it("can reference both scopes as needed ", function() { expect(foo).toEqual(bar); }); }); });
  • 14.
    Disabling xdescribe("A spec", function(){ var foo; beforeEach(function() { foo = 0; foo += 1; }); xit("is just a function, so it can contain any code", function() { expect(foo).toEqual(1); }); });
  • 15.
    describe("A spy", function(){ Spies var foo, bar = null; beforeEach(function() { foo = { setBar: function(value) { bar = value; } }; spyOn(foo, 'setBar'); foo.setBar(123); foo.setBar(456, 'another param'); }); it("tracks that the spy was called", function() { expect(foo.setBar).toHaveBeenCalled(); }); it("tracks its number of calls", function() { expect(foo.setBar.calls.length).toEqual(2); }); it("tracks all the arguments of its calls", function() { expect(foo.setBar).toHaveBeenCalledWith(123); expect(foo.setBar).toHaveBeenCalledWith(456, 'another param'); }); it("allows access to the most recent call", function() { expect(foo.setBar.mostRecentCall.args[0]).toEqual(456); }); it("allows access to other calls", function() { expect(foo.setBar.calls[0].args[0]).toEqual(123); }); it("stops all execution on a function", function() { expect(bar).toBeNull(); }); });
  • 16.
    describe("A spy, whenfaking a return value", function() { var foo, bar, fetchedBar; beforeEach(function() { andReturn foo = { setBar: function(value) { bar = value; }, getBar: function() { return bar; } }; spyOn(foo, 'getBar').andReturn(745); foo.setBar(123); fetchedBar = foo.getBar(); }); it("tracks that the spy was called", function() { expect(foo.getBar).toHaveBeenCalled(); }); it("should not effect other functions", function() { expect(bar).toEqual(123); }); it("when called returns the requested value", function() { expect(fetchedBar).toEqual(745); }); });
  • 17.
    Async tests describe("Asynchronous specs",function() { var value, flag; it("should support async execution of test preparation and exepectations", function() { runs(function() { flag = false; value = 0; setTimeout(function() { flag = true; }, 500); }); waitsFor(function() { value++; return flag; }, "The Value should be incremented", 750); runs(function() { expect(value).toBeGreaterThan(0); }); }); });
  • 18.
  • 19.
  • 20.
    Testing Backbone.js model describe "Todo", -> todo = null ajaxCall = (param) -> jQuery.ajax.mostRecentCall.args[0][param] beforeEach -> todo = new TodoApp.Todo todos = new TodoApp.TodoList [todo] it "should initialize with empty content", -> expect(todo.get "content").toEqual "empty todo..." it "should initialize as not done", -> expect(todo.get "done").toBeFalsy() it "should save after toggle", -> spyOn jQuery, "ajax" todo.toggle() expect(ajaxCall "url").toEqual "/todos" expect(todo.get "done").toBeTruthy()
  • 21.
    and collection describe "TodoList",-> attributes = [ content: "First" done: true , content: "Second" ] todos = null beforeEach -> todos = new TodoApp.TodoList attributes it "should return done todos", -> expect(_.invoke todos.done(), "toJSON").toEqual [attributes[0]] it "should return remaining todos", -> expect(_.invoke todos.remaining(), "toJSON").toEqual [attributes[1]]
  • 22.
    jasmine-jquery • a setof custom matchers for jQuery framework • an API for handling HTML, CSS, and JSON fixtures in your specs
  • 23.
    expect($('<div id="some-id"></div>')).toBe('div#some-id') expect($('<input type="checkbox"checked="checked"/>')).toBeChecked() expect($('<div style="display: none; margin: 10px;"></ div>')).toHaveCss({display: "none", margin: "10px"}) expect($('<option selected="selected"></option>')).toBeSelected() expect($('<div><span class="some-class"></span></ div>')).toContain('span.some-class') expect($('<div class="some-class"></div>')).toHaveClass("some-class") expect($('<div><span></span></div>')).toHaveHtml('<span></span>') expect($('<div><ul></ul><h1>header</h1></div>')).toContainHtml('<ul></ ul>') expect($('<div>some text</div>')).toHaveText('some text') expect($('<input type="text" value="some text"/>')).toHaveValue('some text') expect('<input type='submit' disabled='disabled'/>').toBeDisabled() expect($('<input type='text' />').focus()).toBeFocused()
  • 24.
    HTML fixtures In myfixture.htmlfile: <div id="my-fixture">some complex content here</div> Inside your test: loadFixtures('myfixture.html'); $('#my-fixture').myTestedPlugin(); expect($('#my-fixture')).to...;
  • 25.
    jasmine-fixture // Let's sayyou want to write a Jasmine spec for some code // that needs to select elements from the DOM with jQuery: $('#toddler .hidden.toy input[name="toyName"][value="cuddle bunny"]') // In the good ol' days of manually crafting your HTML fixtures, // you'd have to append some raw HTML to the DOM like this: beforeEach(function(){ $('<div id="toddler"><div class="hidden toy"><input name="toyName" value="cuddle bunny"></div></div>').appendTo('body'); }); afterEach(function(){ $('#toddler').remove() }); // But jasmine-fixture's affix method lets you do this instead: beforeEach(function(){ affix('#toddler .hidden.toy input[name="toyName"][value="cuddle bunny"]') });
  • 26.
    jasmine-given describe("assigning stuff tothis", function() { Given(function() { this.number = 24; }); Given(function() { this.number++; }); When(function() { this.number *= 2; }); Then(function() { return this.number === 50; }); // or Then(function() { expect(this.number).toBe(50) }); }); describe("assigning stuff to variables", function() { var subject; Given(function() { subject = []; }); When(function() { subject.push('foo'); }); Then(function() { return subject.length === 1; }); // or Then(function() { expect(subject.length).toBe(1); }); });
  • 27.
    jasmine-given describe "assigning stuffto this", -> Given -> @number = 24 Given -> @number++ When -> @number *= 2 Then -> @number == 50 # or Then -> expect(@number).toBe(50) describe "assigning stuff to variables", -> subject=null Given -> subject = [] When -> subject.push('foo') Then -> subject.length == 1 # or Then -> expect(subject.length).toBe(1)
  • 28.
    jasmine-stealth describe("multiple stubbings", function(){ var someSpy; beforeEach(function() { someSpy = jasmine.createSpy(); someSpy.when("pirate", { booty: ["jewels",jasmine.any(String)]}).thenReturn("argh!"); someSpy.when("panda",1).thenReturn("sad"); }); it("stubs the first accurately", function() { expect(someSpy("pirate",{ booty: ["jewels","coins"]})).toBe("argh!"); }); it("stubs the second too", function() { expect(someSpy("panda",1)).toBe("sad"); }); it("doesn't return anything when a stubbing isn't satisfied",function(){ expect(someSpy("anything else at all")).not.toBeDefined(); }); });
  • 29.
    References http://pivotal.github.io/jasmine/ http://searls.testdouble.com/posts/2013-03-21- jasmine-tactics-screencast.html https://github.com/rsim/backbone_coffeescript_demo https://github.com/rsim/rpn_calculator http://vimeo.com/53655205