Testing in JavaScript

1,136 views

Published on

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.

Published in: Technology
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
1,136
On SlideShare
0
From Embeds
0
Number of Embeds
3
Actions
Shares
0
Downloads
9
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Testing in JavaScript

  1. 1. JS Unit Testing For both Backend and Frontend
  2. 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. 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. 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. 5. What I’m using ● Mocha ○ works with node ○ works with browser(s) (karma) ● Chai-TDD ○ closest to ruby expect
  6. 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. 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. 8. Test with a browser ● Create a test.html ● Include ○ mocha.js ○ chai.js ○ user_model.js ○ user_model_test.js ● open test.html
  9. 9. open test.html
  10. 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. 11. In the browser you should ● include sinon.js ○ spy/stub/mock library ● include sinon server ○ xhr mocking server
  12. 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(); }); });
  13. 13. open test.html
  14. 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 };
  15. 15. karma start
  16. 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. 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); }); }); ....
  18. 18. mocha
  19. 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. 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. 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. 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. 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); }); }); ....
  24. 24. karma start | mocha
  25. 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)
  26. 26. Grunt example You 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']
  27. 27. grunt test
  28. 28. 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
  29. 29. grunt watch
  30. 30. https://github.com/Valetudox/js_unit_testing we are hiring :)

×