Workshop JavaScript Testing. Frameworks. Client vs Server Testing. Jasmine. Chai. Nock. Sinon. Spec Runners: Karma. TDD. Code coverage. Building a testable JS app.
Presentado por ing: Raúl Delgado y Mario García
1. 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
2. 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
3. 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!
4. 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
5. Tests should be
Fast to run
Easy to understand
Isolated
Not reliant on an Internet connection
6. 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
7. More information in...
● Test-Driven JavaScript Development, by Christian Johansen
● https://en.wikipedia.org/wiki/Software_testing
8. 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
10. 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);
});
});
11. 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);
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("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
});
});
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("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
});
});
17. 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']);
});
});
18. 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);
});
});
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 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();
});
});
21. 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);
});
});
23. 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);
});
});
24. 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();
});
});
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/
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37. 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
38. The cycle of TDD
Write a test
Run tests. Watch the new test fail
Make the test pass
Refactor to remove duplication
39. Benefits of TDD
Produces code that works
Honors the Single Responsibility Principle
Forces conscious development
Productivity boost
41. 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);
});
});
42. 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);
});
43. 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);
});
});
});
46. 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?
47. THANKS FOR YOUR ATTENTION
Leave your questions on the comments section