KEMBAR78
Workshop 5: JavaScript testing | PDF
Front End Workshops
VI.JavaScript testing. Client vs.
Server testing. Test Driven
Development
Raúl Delgado Astudillo
rdelgado@visual-engin.com
Mario García Martín
mgarcia@visual-engin.com
JavaScript testing
“Testing is an infinite process of comparing the invisible to the
ambiguous in order to avoid the unthinkable happening to the
anonymous.”
— James Bach
What is a test?
Type some code
Open and load the browser
Prove functionality
A test is (simply) the validation of an expectation.
Manual testing...
...is
NOT
enough!
Can we do better?
Manual testing is...
Time consuming
Error prone
Irreproducible
(Nearly) Impossible if we want to test a
wide set of browsers and platforms
YES!!
Automated
testing
Tests should be
Fast to run
Easy to understand
Isolated
Not reliant on an Internet connection
Benefits and pitfalls of testing
Regression testing
Refactoring
Cross-browser testing
Good documentation
Helps us write cleaner
interfaces (testable code)
Writing good tests can be
challenging
More information in...
● Test-Driven JavaScript Development, by Christian Johansen
● https://en.wikipedia.org/wiki/Software_testing
Client testing
“A passing test doesn't mean no problem. It means no problem
observed. This time. With these inputs. So far. On my machine.”
— Michael Bolton
Frameworks
Jasmine — Scaffolding
describe("A suite with setup and tear-down", function() {
var foo;
beforeAll(function() {});
afterAll(function() {});
beforeEach(function() {
foo = 1;
});
afterEach(function() {
foo = 0;
});
it("can contain specs with one or more expectations", function() {
expect(foo).toBe(1);
expect(true).toBe(true);
});
});
Matchers
expect(3).toBe(3); // Compares with ===
expect({a: 3}).toEqual({a: 3}); // For comparison of objects
expect('barely').toMatch(/bar/); // For regular expressions
expect(null).toBeDefined(); // Compares against undefined
expect(undefined).toBeUndefined(); // Compares against undefined
expect(null).toBeNull(); // Compares against null
expect('hello').toBeTruthy(); // For boolean casting testing
expect('').toBeFalsy(); // For boolean casting testing
expect(['bar', 'foo']).toContain('bar'); // For finding an item in an Array
expect(2).toBeLessThan(3); // For mathematical comparisons
expect(3).toBeGreaterThan(2); // For mathematical comparisons
expect(3.14).toBeCloseTo(3.17, 1); // For precision math comparison
// For testing if a function throws an exception
expect(function() { throw new Error('Error!'); }).toThrow();
// Modifier 'not'
expect(false).not.toBe(true);
Spies
describe("A suite", function() {
var foo, bar = null;
beforeEach(function() {
foo = { setBar: function(value) { bar = value; } };
spyOn(foo, 'setBar');
foo.setBar(123);
foo.setBar(456, 'another param');
});
it("that defines a spy out of the box", function() {
expect(foo.setBar).toHaveBeenCalled(); // tracks that the spy was called
// tracks all the arguments of its calls
expect(foo.setBar).toHaveBeenCalledWith(123);
expect(foo.setBar).toHaveBeenCalledWith(456, 'another param');
expect(bar).toBeNull(); // stops all execution on a function
});
});
Spies — and.callthrough
describe("A suite", function() {
var foo, bar = null;
beforeEach(function() {
foo = {
setBar: function(value) { bar = value; }
};
spyOn(foo, 'setBar').and.callThrough();
foo.setBar(123);
});
it("that defines a spy configured to call through", function() {
expect(foo.setBar).toHaveBeenCalled(); // tracks that the spy was called
expect(bar).toEqual(123); // the spied function has been called
});
});
describe("A suite", function() {
var foo, bar = null;
beforeEach(function() {
foo = {
getBar: function() { return bar; }
};
spyOn(foo, 'getBar').and.returnValue(745);
});
it("that defines a spy configured to fake a return value", function() {
expect(foo.getBar()).toBe(745); // when called returns the requested value
expect(bar).toBeNull(); // should not affect the variable
});
});
Spies — and.returnValue
describe("A suite", function() {
var foo, bar = null;
beforeEach(function() {
foo = {
setBar: function(value) { bar = value; }
};
spyOn(foo, 'setBar').and.callFake(function() {
console.log('hello');
});
foo.setBar(); // logs hello in the console.
});
it("that defines a spy configured with an alternate implementation", function() {
expect(foo.setBar).toHaveBeenCalled(); // tracks that the spy was called
expect(bar).toBeNull(); // should not affect the variable
});
});
Spies — and.callFake
Spies — createSpy
describe("A suite", function() {
var spy;
beforeAll(function() {
$(window).on('resize', function() { $(window).trigger('myEvent'); });
});
afterAll(function() {
$(window).off('resize');
});
beforeEach(function() {
spy = jasmine.createSpy();
});
it("that defines a spy created manually", function() {
$(window).on('myEvent', spy);
$(window).trigger('resize');
expect(spy).toHaveBeenCalled(); // tracks that the spy was called
});
});
Spies — Other tracking properties (I)
describe("A spy", function() {
var foo, bar = null;
beforeEach(function() {
foo = { setBar: function(value) { bar = value; } };
spyOn(foo, 'setBar');
foo.setBar(123);
foo.setBar(456, 'baz');
});
it("has a rich set of tracking properties", function() {
expect(foo.setBar.calls.count()).toEqual(2); // tracks the number of calls
// tracks the args of each call
expect(foo.setBar.calls.argsFor(0)).toEqual([123]);
expect(foo.setBar.calls.argsFor(1)).toEqual([456, 'baz']);
// has shortcuts to the first and most recent call
expect(foo.setBar.calls.first().args).toEqual([123]);
expect(foo.setBar.calls.mostRecent().args).toEqual([456, 'baz']);
});
});
Spies — Other tracking properties (II)
describe("A spy", function() {
var foo, bar = null;
beforeEach(function() {
foo = { setBar: function(value) { bar = value; } };
spyOn(foo, 'setBar');
foo.setBar(123);
foo.setBar(456, 'baz');
});
it("has a rich set of tracking properties", function() {
// tracks the context and return values
expect(foo.setBar.calls.first().object).toEqual(foo);
expect(foo.setBar.calls.first().returnValue).toBeUndefined();
// can be reset
foo.setBar.calls.reset();
expect(foo.setBar.calls.count()).toBe(0);
});
});
Asynchronous support
describe("Asynchronous specs", function() {
var value;
beforeEach(function(done) {
setTimeout(function() {
value = 0;
done();
}, 100);
});
it("should support async execution of preparation and expectations", function(done) {
expect(value).toBe(0);
done();
});
});
Clock
describe("Manually ticking the Jasmine Clock", function() {
var timerCallback;
beforeEach(function() {
timerCallback = jasmine.createSpy();
jasmine.clock().install();
});
afterEach(function() {
jasmine.clock().uninstall();
});
it("causes a timeout to be called synchronously", function() {
setTimeout(timerCallback, 100);
expect(timerCallback).not.toHaveBeenCalled();
jasmine.clock().tick(101);
expect(timerCallback).toHaveBeenCalled();
});
});
Clock — Mocking the date
describe("Mocking the Date object", function() {
beforeEach(function() {
jasmine.clock().install();
});
afterEach(function() {
jasmine.clock().uninstall();
});
it("mocks the Date object and sets it to a given time", function() {
var baseTime = new Date(2013, 9, 23);
jasmine.clock().mockDate(baseTime);
jasmine.clock().tick(50);
expect(new Date().getTime()).toEqual(baseTime.getTime() + 50);
});
});
Sinon — Spies and Stubs
var spy = sinon.spy();
sinon.spy($, 'ajax');
$.ajax.restore();
sinon.stub($, 'ajax');
$.ajax.restore();
sinon.stub($, 'ajax', function(options) {
console.log(options.url);
});
$.ajax.restore();
Sinon — Fake timer
describe("Manually ticking the Clock", function() {
var clock, timerCallback;
beforeEach(function() {
timerCallback = sinon.spy();
clock = sinon.useFakeTimers();
});
afterEach(function() {
clock.restore();
});
it("causes a timeout to be called synchronously", function() {
setTimeout(timerCallback, 100);
expect(timerCallback.callCount).toBe(0);
clock.tick(101);
expect(timerCallback.callCount).toBe(1);
expect(new Date().getTime()).toBe(101);
});
});
Sinon — Fake server
describe("A suite with a sinon fakeServer", function() {
var server;
beforeEach(function() {
server = sinon.fakeServer.create();
server.autoRespond = true;
server.respondWith(function(xhr) {
xhr.respond(200, {'Content-Type':'application/json'}, JSON.stringify({'msg': 'msg'}));
});
server.xhr.useFilters = true;
server.xhr.addFilter(function(method, url) {
return !!url.match(/fixtures|css/); // If returns true the request will not be faked.
});
});
afterEach(function() {
server.restore();
});
});
More information in...
● https://en.wikipedia.org/wiki/List_of_unit_testing_frameworks#JavaScri
pt
● http://stackoverflow.com/questions/300855/javascript-unit-test-tools-
for-tdd
● http://jasmine.github.io/
● http://sinonjs.org/
Test Driven Development
“The best TDD can do is assure that the code does what the
programmer thinks it should do. That is pretty good by the way.”
— James Grenning
The cycle of TDD
Write a test
Run tests. Watch the new test fail
Make the test pass
Refactor to remove duplication
Benefits of TDD
Produces code that works
Honors the Single Responsibility Principle
Forces conscious development
Productivity boost
More information in...
● Test-Driven Development By Example, by Kent Beck.
Jasmine Disabling specs
xdescribe("A disabled suite", function() {
it("where the specs will not be executed", function() {
expect(2).toEqual(1);
});
});
describe("A suite", function() {
xit("with a disabled spec declared with 'xit'", function() {
expect(true).toBe(false);
});
it.only("with a spec that will be executed", function() {
expect(1).toBe(1);
});
it("with another spec that will not be executed", function() {
expect(1).toBe(1);
});
});
Asynchronous support
describe("long asynchronous specs", function() {
beforeEach(function(done) {
done();
}, 1000);
afterEach(function(done) {
done();
}, 1000);
it("takes a long time", function(done) {
setTimeout(done, 9000);
}, 10000);
});
Asynchronous support. Jasmine 1.3
describe("Asynchronous specs", function() {
var value, flag;
it("should support async execution of test preparation and expectations", 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.Clock v.1.3
it('description', function() {
jasmine.Clock.useMock();
setTimeout(function() {
console.log('print something');
}, 200);
jasmine.Clock.tick(190);
});
it('description', function() {
jasmine.Clock.useMock();
jasmine.Clock.tick(190);
});
Karma
npm install karma --save-dev
npm install karma-jasmine karma-chrome-launcher karma-phantomjs-launcher --save-dev
npm install karma-coverage --save-dev
npm install -g karma-cli
Installation
Configuration
karma init karma.conf.js npm install grunt-karma --save-dev
grunt.loadNpmTasks('grunt-karma');
karma: {
unit: {
configFile: 'karma.conf.js'
}
}
Grunt task
Karma configuration
The files array determines which files are included in the browser and which files are watched and
served by Karma.
Each pattern is either a simple string or an object with four properties:
pattern String, no default value. The pattern to use for matching. This property is mandatory.
watched Boolean (true). If autoWatch is true all files that have set watched to true will be watched
for changes.
included Boolean (true). Should the files be included in the browser using <script> tag? Use false if
you want to load them manually, eg. using Require.js.
served Boolean (true). Should the files be served by Karma's webserver?
THANKS FOR YOUR ATTENTION
Leave your questions on the comments section
Workshop 5: JavaScript testing

Workshop 5: JavaScript testing

  • 1.
    Front End Workshops VI.JavaScripttesting. Client vs. Server testing. Test Driven Development Raúl Delgado Astudillo rdelgado@visual-engin.com Mario García Martín mgarcia@visual-engin.com
  • 2.
    JavaScript testing “Testing isan infinite process of comparing the invisible to the ambiguous in order to avoid the unthinkable happening to the anonymous.” — James Bach
  • 3.
    What is atest? Type some code Open and load the browser Prove functionality A test is (simply) the validation of an expectation. Manual testing... ...is NOT enough!
  • 4.
    Can we dobetter? Manual testing is... Time consuming Error prone Irreproducible (Nearly) Impossible if we want to test a wide set of browsers and platforms YES!! Automated testing
  • 5.
    Tests should be Fastto run Easy to understand Isolated Not reliant on an Internet connection
  • 6.
    Benefits and pitfallsof testing Regression testing Refactoring Cross-browser testing Good documentation Helps us write cleaner interfaces (testable code) Writing good tests can be challenging
  • 7.
    More information in... ●Test-Driven JavaScript Development, by Christian Johansen ● https://en.wikipedia.org/wiki/Software_testing
  • 8.
    Client testing “A passingtest doesn't mean no problem. It means no problem observed. This time. With these inputs. So far. On my machine.” — Michael Bolton
  • 9.
  • 10.
    Jasmine — Scaffolding describe("Asuite with setup and tear-down", function() { var foo; beforeAll(function() {}); afterAll(function() {}); beforeEach(function() { foo = 1; }); afterEach(function() { foo = 0; }); it("can contain specs with one or more expectations", function() { expect(foo).toBe(1); expect(true).toBe(true); }); });
  • 11.
    Matchers expect(3).toBe(3); // Compareswith === expect({a: 3}).toEqual({a: 3}); // For comparison of objects expect('barely').toMatch(/bar/); // For regular expressions expect(null).toBeDefined(); // Compares against undefined expect(undefined).toBeUndefined(); // Compares against undefined expect(null).toBeNull(); // Compares against null expect('hello').toBeTruthy(); // For boolean casting testing expect('').toBeFalsy(); // For boolean casting testing expect(['bar', 'foo']).toContain('bar'); // For finding an item in an Array expect(2).toBeLessThan(3); // For mathematical comparisons expect(3).toBeGreaterThan(2); // For mathematical comparisons expect(3.14).toBeCloseTo(3.17, 1); // For precision math comparison // For testing if a function throws an exception expect(function() { throw new Error('Error!'); }).toThrow(); // Modifier 'not' expect(false).not.toBe(true);
  • 12.
    Spies describe("A suite", function(){ var foo, bar = null; beforeEach(function() { foo = { setBar: function(value) { bar = value; } }; spyOn(foo, 'setBar'); foo.setBar(123); foo.setBar(456, 'another param'); }); it("that defines a spy out of the box", function() { expect(foo.setBar).toHaveBeenCalled(); // tracks that the spy was called // tracks all the arguments of its calls expect(foo.setBar).toHaveBeenCalledWith(123); expect(foo.setBar).toHaveBeenCalledWith(456, 'another param'); expect(bar).toBeNull(); // stops all execution on a function }); });
  • 13.
    Spies — and.callthrough describe("Asuite", function() { var foo, bar = null; beforeEach(function() { foo = { setBar: function(value) { bar = value; } }; spyOn(foo, 'setBar').and.callThrough(); foo.setBar(123); }); it("that defines a spy configured to call through", function() { expect(foo.setBar).toHaveBeenCalled(); // tracks that the spy was called expect(bar).toEqual(123); // the spied function has been called }); });
  • 14.
    describe("A suite", function(){ var foo, bar = null; beforeEach(function() { foo = { getBar: function() { return bar; } }; spyOn(foo, 'getBar').and.returnValue(745); }); it("that defines a spy configured to fake a return value", function() { expect(foo.getBar()).toBe(745); // when called returns the requested value expect(bar).toBeNull(); // should not affect the variable }); }); Spies — and.returnValue
  • 15.
    describe("A suite", function(){ var foo, bar = null; beforeEach(function() { foo = { setBar: function(value) { bar = value; } }; spyOn(foo, 'setBar').and.callFake(function() { console.log('hello'); }); foo.setBar(); // logs hello in the console. }); it("that defines a spy configured with an alternate implementation", function() { expect(foo.setBar).toHaveBeenCalled(); // tracks that the spy was called expect(bar).toBeNull(); // should not affect the variable }); }); Spies — and.callFake
  • 16.
    Spies — createSpy describe("Asuite", function() { var spy; beforeAll(function() { $(window).on('resize', function() { $(window).trigger('myEvent'); }); }); afterAll(function() { $(window).off('resize'); }); beforeEach(function() { spy = jasmine.createSpy(); }); it("that defines a spy created manually", function() { $(window).on('myEvent', spy); $(window).trigger('resize'); expect(spy).toHaveBeenCalled(); // tracks that the spy was called }); });
  • 17.
    Spies — Othertracking properties (I) describe("A spy", function() { var foo, bar = null; beforeEach(function() { foo = { setBar: function(value) { bar = value; } }; spyOn(foo, 'setBar'); foo.setBar(123); foo.setBar(456, 'baz'); }); it("has a rich set of tracking properties", function() { expect(foo.setBar.calls.count()).toEqual(2); // tracks the number of calls // tracks the args of each call expect(foo.setBar.calls.argsFor(0)).toEqual([123]); expect(foo.setBar.calls.argsFor(1)).toEqual([456, 'baz']); // has shortcuts to the first and most recent call expect(foo.setBar.calls.first().args).toEqual([123]); expect(foo.setBar.calls.mostRecent().args).toEqual([456, 'baz']); }); });
  • 18.
    Spies — Othertracking properties (II) describe("A spy", function() { var foo, bar = null; beforeEach(function() { foo = { setBar: function(value) { bar = value; } }; spyOn(foo, 'setBar'); foo.setBar(123); foo.setBar(456, 'baz'); }); it("has a rich set of tracking properties", function() { // tracks the context and return values expect(foo.setBar.calls.first().object).toEqual(foo); expect(foo.setBar.calls.first().returnValue).toBeUndefined(); // can be reset foo.setBar.calls.reset(); expect(foo.setBar.calls.count()).toBe(0); }); });
  • 19.
    Asynchronous support describe("Asynchronous specs",function() { var value; beforeEach(function(done) { setTimeout(function() { value = 0; done(); }, 100); }); it("should support async execution of preparation and expectations", function(done) { expect(value).toBe(0); done(); }); });
  • 20.
    Clock describe("Manually ticking theJasmine Clock", function() { var timerCallback; beforeEach(function() { timerCallback = jasmine.createSpy(); jasmine.clock().install(); }); afterEach(function() { jasmine.clock().uninstall(); }); it("causes a timeout to be called synchronously", function() { setTimeout(timerCallback, 100); expect(timerCallback).not.toHaveBeenCalled(); jasmine.clock().tick(101); expect(timerCallback).toHaveBeenCalled(); }); });
  • 21.
    Clock — Mockingthe date describe("Mocking the Date object", function() { beforeEach(function() { jasmine.clock().install(); }); afterEach(function() { jasmine.clock().uninstall(); }); it("mocks the Date object and sets it to a given time", function() { var baseTime = new Date(2013, 9, 23); jasmine.clock().mockDate(baseTime); jasmine.clock().tick(50); expect(new Date().getTime()).toEqual(baseTime.getTime() + 50); }); });
  • 22.
    Sinon — Spiesand Stubs var spy = sinon.spy(); sinon.spy($, 'ajax'); $.ajax.restore(); sinon.stub($, 'ajax'); $.ajax.restore(); sinon.stub($, 'ajax', function(options) { console.log(options.url); }); $.ajax.restore();
  • 23.
    Sinon — Faketimer describe("Manually ticking the Clock", function() { var clock, timerCallback; beforeEach(function() { timerCallback = sinon.spy(); clock = sinon.useFakeTimers(); }); afterEach(function() { clock.restore(); }); it("causes a timeout to be called synchronously", function() { setTimeout(timerCallback, 100); expect(timerCallback.callCount).toBe(0); clock.tick(101); expect(timerCallback.callCount).toBe(1); expect(new Date().getTime()).toBe(101); }); });
  • 24.
    Sinon — Fakeserver describe("A suite with a sinon fakeServer", function() { var server; beforeEach(function() { server = sinon.fakeServer.create(); server.autoRespond = true; server.respondWith(function(xhr) { xhr.respond(200, {'Content-Type':'application/json'}, JSON.stringify({'msg': 'msg'})); }); server.xhr.useFilters = true; server.xhr.addFilter(function(method, url) { return !!url.match(/fixtures|css/); // If returns true the request will not be faked. }); }); afterEach(function() { server.restore(); }); });
  • 25.
    More information in... ●https://en.wikipedia.org/wiki/List_of_unit_testing_frameworks#JavaScri pt ● http://stackoverflow.com/questions/300855/javascript-unit-test-tools- for-tdd ● http://jasmine.github.io/ ● http://sinonjs.org/
  • 37.
    Test Driven Development “Thebest TDD can do is assure that the code does what the programmer thinks it should do. That is pretty good by the way.” — James Grenning
  • 38.
    The cycle ofTDD Write a test Run tests. Watch the new test fail Make the test pass Refactor to remove duplication
  • 39.
    Benefits of TDD Producescode that works Honors the Single Responsibility Principle Forces conscious development Productivity boost
  • 40.
    More information in... ●Test-Driven Development By Example, by Kent Beck.
  • 41.
    Jasmine Disabling specs xdescribe("Adisabled suite", function() { it("where the specs will not be executed", function() { expect(2).toEqual(1); }); }); describe("A suite", function() { xit("with a disabled spec declared with 'xit'", function() { expect(true).toBe(false); }); it.only("with a spec that will be executed", function() { expect(1).toBe(1); }); it("with another spec that will not be executed", function() { expect(1).toBe(1); }); });
  • 42.
    Asynchronous support describe("long asynchronousspecs", function() { beforeEach(function(done) { done(); }, 1000); afterEach(function(done) { done(); }, 1000); it("takes a long time", function(done) { setTimeout(done, 9000); }, 10000); });
  • 43.
    Asynchronous support. Jasmine1.3 describe("Asynchronous specs", function() { var value, flag; it("should support async execution of test preparation and expectations", 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); }); }); });
  • 44.
    jasmine.Clock v.1.3 it('description', function(){ jasmine.Clock.useMock(); setTimeout(function() { console.log('print something'); }, 200); jasmine.Clock.tick(190); }); it('description', function() { jasmine.Clock.useMock(); jasmine.Clock.tick(190); });
  • 45.
    Karma npm install karma--save-dev npm install karma-jasmine karma-chrome-launcher karma-phantomjs-launcher --save-dev npm install karma-coverage --save-dev npm install -g karma-cli Installation Configuration karma init karma.conf.js npm install grunt-karma --save-dev grunt.loadNpmTasks('grunt-karma'); karma: { unit: { configFile: 'karma.conf.js' } } Grunt task
  • 46.
    Karma configuration The filesarray determines which files are included in the browser and which files are watched and served by Karma. Each pattern is either a simple string or an object with four properties: pattern String, no default value. The pattern to use for matching. This property is mandatory. watched Boolean (true). If autoWatch is true all files that have set watched to true will be watched for changes. included Boolean (true). Should the files be included in the browser using <script> tag? Use false if you want to load them manually, eg. using Require.js. served Boolean (true). Should the files be served by Karma's webserver?
  • 47.
    THANKS FOR YOURATTENTION Leave your questions on the comments section