30
JS Unit Testing For both Backend and Frontend

Testing in JavaScript

Embed Size (px)

DESCRIPTION

Here at Digital Natives we are devoted to support the automated testing of our applications. Lately we write more and more complex business logics on front-end side therefore we need to test front-end side codes more accurately. I put together a presentation for our weekly developer meeting concerning this topic, where I reviewed the current possibilities, but I think that it might be interesting for other front-end programmers too.

Citation preview

Page 1: Testing in JavaScript

JS Unit TestingFor both Backend and Frontend

Page 2: Testing in JavaScript

Why is unit testing good for js?

● you can develop without a browser● you can test your code automatically● you don’t have to test manually● you don’t have to test via E2E test● you can develop without a browser

○ of course for view development you have to use● you can test you BE code

Page 3: Testing in JavaScript

Prerequisite (conditions)

● well separated code○ like mvc (no spaghetti code)○ small, testable parts (classes)

● zero or minimal dom dependency○ dom is slow ○ very slow and you don’t want to mock it out

Page 4: Testing in JavaScript

How to test JS?

● you need a library○ mocha○ jasmine○ Qunit○ etc

● you have to run your tests○ browser○ node○ karma (odd one out)○ etc

Page 5: Testing in JavaScript

What I’m using

● Mocha○ works with node○ works with browser(s) (karma)

● Chai-TDD○ closest to ruby expect

Page 6: Testing in JavaScript

Example JS code - User Modelvar User;

User = (function() {

function User(plainObject) { this.parse(plainObject); }

User.prototype.parse = function(plainObject) { this.id = plainObject.id; this.first_name = plainObject.first_name; this.last_name = plainObject.last_name; this.status = plainObject.status; };

return User;})();

Page 7: Testing in JavaScript

How can we test Our Model?describe('User/Model', function() { var dummyUserData = { id: 1, first_name: 'First', last_name: 'Last', status: 'locked' };

describe('#initialize', function() { it('should set the provided fields', function() { var user = new User(dummyUserData);

expect(user.first_name).to.eq('First'); expect(user.last_name).to.eq('Last'); expect(user.status).to.eq('locked'); }); });});

Page 8: Testing in JavaScript

Test with a browser

● Create a test.html● Include

○ mocha.js○ chai.js○ user_model.js○ user_model_test.js

● open test.html

Page 9: Testing in JavaScript

open test.html

Page 10: Testing in JavaScript

How to use xhr in your model?User.prototype.get = function(cbSuccess, cbError) { var self = this;

$.get("/users/" + this.id, function(data) { self.parse(data); cbSuccess(); }).fail(function(error, m) { cbError(); }); };

Page 11: Testing in JavaScript

In the browser you should

● include sinon.js○ spy/stub/mock library

● include sinon server○ xhr mocking server

Page 12: Testing in JavaScript

How to test async call? describe('with success xhr', function() { beforeEach(function() {

var response = '{ "id": 1, "first_name": "Second First", "last_name": "Second Last", "status": "active" }';

server = sinon.fakeServer.create(); server.respondWith("GET", "/users/1", [200, { "Content-Type": "application/json" }, response ]);

});

afterEach(function() {

server.restore();

});

it('should set the newly provided data', function(done) { user.get(function() { expect(user.first_name).to.eq('Second First');

.... done(); }); server.respond(); }); });

Page 13: Testing in JavaScript

open test.html

Page 14: Testing in JavaScript

Test with multiple browsers (karma)

● open the browsers(silently)

● run your tests● close the browsers

(optional)● rerun your tests on file

changes (optional)● outputs results to your

console

karma.conf.js

{ frameworks: ['mocha', 'chai'], files: [ 'vendor/**/*.js', 'user_model.js', 'user_model_tests.js' ], autoWatch: false, browsers: ['Chrome', 'Firefox'], singleRun: true};

Page 15: Testing in JavaScript

karma start

Page 16: Testing in JavaScript

Test with node

● basically the same as in browsers○ only difference is the module system require

(‘module’) ● faster than browsers● you can test your BE and FE with the same

test runner ○ if you doesn’t use browser specific stuff (like xhr,

window, dom etc)

Page 17: Testing in JavaScript

Test with nodevar request = require("superagent");var User;

User = (function() { ... User.prototype.get = function(cbSuccess) { var url = "/users/" + this.id;

request.get(url, function(res) { this.parse(res); cbSuccess(); }.bind(this)); }; return User;})();

module.exports = User;

var chai = require('chai');var expect = chai.expect;var sinon = require('sinon');

var User = require('../src/user');var request = require("superagent");

describe('User/Model', function() { beforeEach(function() {

var response = var response = '{ "id": 1, "first_name": "Second First", "last_name": "Second Last", "status": "active" }'; sinon.stub(request, "get").yields(response); });});

....

Page 18: Testing in JavaScript

mocha

Page 19: Testing in JavaScript

● While I develop I want to to test my code via node○ a lot faster○ easier to test partials

● When I build I want to test my code via karma○ we have to know if something is wrong in IE

Same tests with node and karma

Page 20: Testing in JavaScript

Browserify

● you can use nodejs module syntax on the frontend○ var UserModel = require(“Modules/User/Model”);○ var userModel = new UserModel();

● generate one js file with all the dependencies● you can use same libraries on the FE and on

the BE part○ moment.js○ schemata (js validation library)○ your custom lib

Page 21: Testing in JavaScript

Schemate schema def.var schemata = require('schemata');

var UserSchema = schemata({ first_name: { name: 'First Name', type: String, validators: { all: [req] } }, last_name: { name: 'Last Name', type: String, validators: { all: [req] } }, status: { type: String, default: 'locked', validators: { all: [req] } }});

module.exports = UserSchema;

REQUIRE VALIDATOR

var req = function(key, keyDisplayName, object, callback) { var value = object[key]; if (typeof value !== “undefined” && value !== null){ return callback(null, undefined); } else { return callback(null, ' is required' ); }};

FE

buttonClick = function(){

var json = this.toJson();

UserSchema.validate(json, function(errors){ if (Object.keys(errors).length === 0) return sendAjaxToTheServer(json); showErrors(errors); });

}

Page 22: Testing in JavaScript

Use browserify with mocha

● everything is the same as in the node tests

● you have to use karma preprocessor

{ frameworks: ['mocha', 'browserify'], preprocessors: { 'test/*': ['browserify'] }

....};

Page 23: Testing in JavaScript

Test with node / karma var request = require("superagent");var User;

User = (function() { ... User.prototype.get = function(cbSuccess) { var url = "/users/" + this.id;

request.get(url, function(res) { this.parse(res); cbSuccess(); }.bind(this)); }; return User;})();

module.exports = User;

var chai = require('chai');var expect = chai.expect;var sinon = require('sinon');

var User = require('../src/user');var request = require("superagent");

describe('User/Model', function() { beforeEach(function() {

var response = var response = '{ "id": 1, "first_name": "Second First", "last_name": "Second Last", "status": "active" }'; sinon.stub(request, "get").yields(response); });});

....

Page 24: Testing in JavaScript

karma start | mocha

Page 25: Testing in JavaScript

Grunt - Config based Task runner

● There are a lot of contributed tasks○ mochaTest, karma, browserify, concatenate, copy, ftp,

sass, less, etc● you can define complex tasks

○ build (jshint, concatenate, test:unit, test:e2e)○ test (jshint, test:unit)○ deploy (build, ftp)

Page 26: Testing in JavaScript

Grunt exampleYou can create complex tasks

test:

- 'karma' - 'mochaTest'

build: - 'jshint' - 'test' - 'browserify' - 'concatenate' - 'minify' - 'sass' - 'copy'

Simple task, using grunt-contrib-mocha

configuration file:

mochaTest: feTest: options: clearRequireCache: true reporter: 'spec' src: ['test/fe/**/*.js']

beTest: options: clearRequireCache: true reporter: 'dots' src: ['test/be/**/*.js']

Page 27: Testing in JavaScript

grunt test

Page 28: Testing in JavaScript

Grunt watch

● watches for file changes

● runs tasks if one of the specified files have changed

watch: scripts: files: ['test/unit/**/*.js', 'src/js/**/*.js'] tasks: ['jshint', 'mocha'] interrupt: true options: spawn: false jade: files: ['src/view/**/*.jade'] tasks: ['jade', ‘livereload:html’] interrupt: true options: spawn: false stylus: files: ['src/style/**/*.styl'] tasks: ['stylus', ‘livereload:css’] interrupt: false options: spawn: false

Page 29: Testing in JavaScript

grunt watch

Page 30: Testing in JavaScript

https://github.com/Valetudox/js_unit_testing

we are hiring :)