Unit Test Your Javascript Codes
riza@thehyve.nl @rizanugraha http://slides.com/rizanugraha/js-unit-testing#/
We are going to discuss:
- Definition
- Pros/Cons
- Testable Javascript Code
- TDD/BDD
- Framework & Tools
- Exercise 1
- AngularJS Unit Testing
- Exercise 2
A unit test is a short program fragment written and maintained by the developers on the product team, which exercises some narrow part of the product's source code and checks the results.
given input X,
is output Y?
Pro's
- Clear perspective on ideal API design
- Improving understanding of the codes
- Push toward refactoring and improving the code itself
- Quality Assurance
- Continues delivery aid
Con's
- Waste of time & productivity
- Learning curve
- It takes more development time to write tests
- Waste of time to fix failed tests when requirements are changed
- Regression testing is more effective
Facts
- Most developer don’t know how to test.
- Lack of knowledge on unit testing (what & how)
- Lack of understanding of the essential ingredients of every unit test.
- Unit testings mostly aren’t considered on estimating a task.
- External pressures (project deadline, PM, etc)
Hacker News Poll
Why we have tendency to deprioritize unit test?
- Hyperbolic Discounting : tendency for people to increasingly choose a smaller-sooner reward over a larger-later reward as the delay occurs sooner rather than later in time.
- Codes are hard to test
What makes codes hard to test?
What makes codes hard to test?
var tmplCache = {};
function loadTemplate (name) {
if (!tmplCache[name]) {
tmplCache[name] = $.get('/templates/' + name);
}
return tmplCache[name];
}
$(function () {
var resultsList = $('#results');
var liked = $('#liked');
var pending = false;
$('#searchForm').on('submit', function (e) {
e.preventDefault();
if (pending) { return; }
var form = $(this);
var query = $.trim( form.find('input[name="q"]').val() );
if (!query) { return; }
pending = true;
$.ajax('/data/search.json', {
data : { q: query },
dataType : 'json',
success : function (data) {
loadTemplate('people-detailed.tmpl').then(function (t) {
var tmpl = _.template(t);
resultsList.html( tmpl({ people : data.results }) );
pending = false;
});
}
});
$('<li>', {
'class' : 'pending',
html : 'Searching …'
}).appendTo( resultsList.empty() );
});
resultsList.on('click', '.like', function (e) {
e.preventDefault();
var name = $(this).closest('li').find('h2').text();
liked.find('.no-results').remove();
$('<li>', { text: name }).appendTo(liked);
});
});
- Annonymous functions, lack of structure
- Complex, oversized functions
- Lack of configurability
- Hidden or share states
- Tightly coupled
What makes codes hard to test?
- Presentation & interaction
- Data management & persistence
- Overall application state
- Setup & glue code to make the pieces work together
- Using MVC framework (AngularJS, ReactJS, etc)
Organising our code : Breaking it up into few different area of responsibility
var Likes = function (el) {
this.el = $(el);
return this;
};
Likes.prototype.add = function (name) {
this.el.find('.no-results').remove();
$('<li>', { text: name }).appendTo(this.el);
};
var SearchResults = function (el) {
this.el = $(el);
this.el.on( 'click', '.btn.like', _.bind(this._handleClick, this) );
};
SearchResults.prototype.setResults = function (results) {
var templateRequest = $.get('people-detailed.tmpl');
templateRequest.then( _.bind(this._populate, this, results) );
};
SearchResults.prototype._handleClick = function (evt) {
var name = $(evt.target).closest('li.result').attr('data-name');
$(document).trigger('like', [ name ]);
};
SearchResults.prototype._populate = function (results, tmpl) {
var html = _.template(tmpl, { people: results });
this.el.html(html);
};
TDD / BDD
TDD : Test-driven Development
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class MyTests {
@Test
public void multiplicationOfZeroIntegersShouldReturnZero() {
MyClass tester = new MyClass(); // MyClass is tested
// assert statements
assertEquals("10 x 0 must be 0", 0, tester.multiply(10, 0));
assertEquals("0 x 10 must be 0", 0, tester.multiply(0, 10));
assertEquals("0 x 0 must be 0", 0, tester.multiply(0, 0));
}
}
BDD : Behaviour-driven Development
BDD is when we write behavior & specification that then drives our software development.
describe('Test', function (){
before(function(){
// Stuff to do before the tests, like imports, what not
});
describe('#factorial()', function (){
it('should return 1 when given 0', function (){
factorial(0).should.equal(1);
});
it('should return 1 when given 1', function (){
factorial(1).should.equal(1);
});
it('should return 2 when given 2', function (){
factorial(2).should.equal(2);
});
it('should return 6 when given 3', function (){
factorial(3).should.equal(6);
});
});
after(function () {
// Anything after the tests have finished
});
});
Javascript Unit Testing Framerworks
- mocha
- jasmine
- qunit
- unit.js
Jasmine is a behavior-driven development framework for testing JavaScript code.
It does not depend on any other JavaScript frameworks.
It does not require a DOM. And it has a clean, obvious syntax so that you can easily write tests.
describe ('AService', function () {
beforeEach(function (){
// Things to do before each test cases
});
afterEach(function (){
// Things to do before each test cases
});
it ('should have something defined', function () {
// Implement test specifications
});
...
describe ('::methodOne', function () {
beforeEach(function (){
// Things to do before each test cases
});
afterEach(function (){
// Things to do before each test cases
});
it ('should do one operation', function () {
// Implement test specifications
});
...
});
});
Excercise 1
https://plnkr.co/edit/ST8SikrbiwnLQWvdmASa?p=info
Spies
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, 'another param');
});
it("tracks that the spy was called", function() {
expect(foo.setBar).toHaveBeenCalled();
});
});
A spy can stub any function and tracks calls to it and all arguments.
Spies
toHaveBeenCalled | return true if the spy was called. | expect(foo.setBar).toHaveBeenCalled(); |
toHaveBeenCalledWith | return true if the argument list matches any of the recorded calls to the spy. | expect(foo.setBar).toHaveBeenCalledWith(456, 'another param'); |
and.callThrough | track all calls to it but in addition it will delegate to the actual implementation. | spyOn(foo, 'getBar').and.callThrough(); expect(foo.getBar).toHaveBeenCalled(); |
and.returnValue | all calls to the function will return a specific value. | spyOn(foo, "getBar").and.returnValue(745); |
and.callFake | all calls to the spy will delegate to the supplied function. | spyOn(foo, "getBar").and.callFake(function() { return 1001; }); |
and.throwError | all calls to the spy will throw the specified value as an error. | spyOn(foo, "setBar").and.throwError("quux"); |
Angular JS
Unit Testing
Angular JS Unit Test
Karma + Jasmine
Karma is a JavaScript command line tool that can be used to spawn a web server which loads your application's source code and executes your tests. You can configure Karma to run against a number of browsers, which is useful for being confident that your application works on all browsers you need to support.
angular-mocks (ng-mock)
to inject and mock Angular services within unit tests.
$httpBackend
- $httpBackend.expect - specifies a request expectation
- $httpBackend.when - specifies a backend definition
- $q, $on, $watch are properties of $rootScope
https://docs.angularjs.org/api/ngMock/service/$httpBackend
https://docs.angularjs.org/guide/unit-testing
Excercise 2
- https://www.agilealliance.org/glossary/unit-test/
- https://www.computer.org/cms/csmediacenter/docs/4-TES_KevlinHenney_ThePerceivedProsandConsofUnitTesting.pdf
- Writing Testable Javascript
- TDD/BDD
- Frameworks & Tools
- Angular Unit Testing exercise : https://github.com/rnugraha/unit-testing-angular
- Factorial exercise : https://plnkr.co/edit/ST8SikrbiwnLQWvdmASa?p=info
JS Unit Testing
By Riza Nugraha
JS Unit Testing
- 1,687