+ All Categories
Home > Software > Workshop 5: JavaScript testing

Workshop 5: JavaScript testing

Date post: 14-Jan-2017
Category:
Upload: visual-engineering
View: 387 times
Download: 1 times
Share this document with a friend
48
Front End Workshops VI.JavaScript testing. Client vs. Server testing. Test Driven Development Raúl Delgado Astudillo [email protected] Mario García Martín [email protected]
Transcript
Page 1: Workshop 5: JavaScript testing

Front End Workshops

VI.JavaScript testing. Client vs. Server testing. Test Driven

Development

Raúl Delgado [email protected]

Mario García Martí[email protected]

Page 2: Workshop 5: JavaScript testing

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

Page 3: Workshop 5: JavaScript testing

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...

...isNOT

enough!

Page 4: Workshop 5: JavaScript testing

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

Page 5: Workshop 5: JavaScript testing

Tests should be

Fast to run

Easy to understand

Isolated

Not reliant on an Internet connection

Page 6: Workshop 5: JavaScript testing

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

Page 7: Workshop 5: JavaScript testing

More information in...

● Test-Driven JavaScript Development, by Christian Johansen

● https://en.wikipedia.org/wiki/Software_testing

Page 8: Workshop 5: JavaScript 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

Page 9: Workshop 5: JavaScript testing

Frameworks

Page 10: Workshop 5: JavaScript testing

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);});

});

Page 11: Workshop 5: JavaScript testing

Matchersexpect(3).toBe(3); // Compares with ===expect({a: 3}).toEqual({a: 3}); // For comparison of objectsexpect('barely').toMatch(/bar/); // For regular expressionsexpect(null).toBeDefined(); // Compares against undefinedexpect(undefined).toBeUndefined(); // Compares against undefinedexpect(null).toBeNull(); // Compares against nullexpect('hello').toBeTruthy(); // For boolean casting testingexpect('').toBeFalsy(); // For boolean casting testingexpect(['bar', 'foo']).toContain('bar'); // For finding an item in an Arrayexpect(2).toBeLessThan(3); // For mathematical comparisonsexpect(3).toBeGreaterThan(2); // For mathematical comparisonsexpect(3.14).toBeCloseTo(3.17, 1); // For precision math comparison

// For testing if a function throws an exceptionexpect(function() { throw new Error('Error!'); }).toThrow();

// Modifier 'not'expect(false).not.toBe(true);

Page 12: Workshop 5: JavaScript testing

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 callsexpect(foo.setBar).toHaveBeenCalledWith(123);expect(foo.setBar).toHaveBeenCalledWith(456, 'another param');

expect(bar).toBeNull(); // stops all execution on a function});

});

Page 13: Workshop 5: JavaScript testing

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});

});

Page 14: Workshop 5: JavaScript testing

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

Page 15: Workshop 5: JavaScript testing

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

Page 16: Workshop 5: JavaScript testing

Spies — createSpydescribe("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

});});

Page 17: Workshop 5: JavaScript testing

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 callexpect(foo.setBar.calls.argsFor(0)).toEqual([123]);expect(foo.setBar.calls.argsFor(1)).toEqual([456, 'baz']);// has shortcuts to the first and most recent callexpect(foo.setBar.calls.first().args).toEqual([123]);

expect(foo.setBar.calls.mostRecent().args).toEqual([456, 'baz']);});

});

Page 18: Workshop 5: JavaScript testing

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 resetfoo.setBar.calls.reset();expect(foo.setBar.calls.count()).toBe(0);

});});

Page 19: Workshop 5: JavaScript testing

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();

});});

Page 20: Workshop 5: JavaScript testing

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();

});});

Page 21: Workshop 5: JavaScript testing

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);

});});

Page 22: Workshop 5: JavaScript testing

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();

Page 23: Workshop 5: JavaScript testing

Sinon — Fake timerdescribe("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);

});});

Page 24: Workshop 5: JavaScript testing

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();

});});

Page 25: Workshop 5: JavaScript testing

More information in...

● https://en.wikipedia.org/wiki/List_of_unit_testing_frameworks#JavaScript

● http://stackoverflow.com/questions/300855/javascript-unit-test-tools-for-tdd

● http://jasmine.github.io/

● http://sinonjs.org/

Page 26: Workshop 5: JavaScript testing
Page 27: Workshop 5: JavaScript testing
Page 28: Workshop 5: JavaScript testing
Page 29: Workshop 5: JavaScript testing
Page 30: Workshop 5: JavaScript testing
Page 31: Workshop 5: JavaScript testing
Page 32: Workshop 5: JavaScript testing
Page 33: Workshop 5: JavaScript testing
Page 34: Workshop 5: JavaScript testing
Page 35: Workshop 5: JavaScript testing
Page 36: Workshop 5: JavaScript testing
Page 37: Workshop 5: JavaScript testing

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

Page 38: Workshop 5: JavaScript testing

The cycle of TDD

Write a test

Run tests. Watch the new test fail

Make the test pass

Refactor to remove duplication

Page 39: Workshop 5: JavaScript testing

Benefits of TDD

Produces code that works

Honors the Single Responsibility Principle

Forces conscious development

Productivity boost

Page 40: Workshop 5: JavaScript testing

More information in...

● Test-Driven Development By Example, by Kent Beck.

Page 41: Workshop 5: JavaScript testing

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);

});});

Page 42: Workshop 5: JavaScript testing

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);});

Page 43: Workshop 5: JavaScript testing

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);});

});});

Page 44: Workshop 5: JavaScript testing

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);});

Page 45: Workshop 5: JavaScript testing

Karma

npm install karma --save-devnpm install karma-jasmine karma-chrome-launcher karma-phantomjs-launcher --save-devnpm install karma-coverage --save-devnpm install -g karma-cli

Installation

Configurationkarma init karma.conf.js npm install grunt-karma --save-dev

grunt.loadNpmTasks('grunt-karma');karma: {

unit: {configFile: 'karma.conf.js'

}}

Grunt task

Page 46: Workshop 5: JavaScript testing

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.

includedBoolean (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?

Page 47: Workshop 5: JavaScript testing

THANKS FOR YOUR ATTENTION

Leave your questions on the comments section

Page 48: Workshop 5: JavaScript testing

Recommended