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.
2. 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
3. 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
4. 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
5. What I’m using
● Mocha
○ works with node
○ works with browser(s) (karma)
● Chai-TDD
○ closest to ruby expect
6. Example JS code - User Model
var 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;
})();
7. 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');
});
});
});
8. Test with a browser
● Create a test.html
● Include
○ mocha.js
○ chai.js
○ user_model.js
○ user_model_test.js
● open test.html
10. 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();
});
};
11. In the browser you should
● include sinon.js
○ spy/stub/mock library
● include sinon server
○ xhr mocking server
12. 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();
});
});
14. 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
};
16. 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)
17. Test with node
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);
});
});
....
19. ● 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
20. 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
21. 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);
});
}
22. 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']
}
....
};
23. 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);
});
});
....
25. 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)