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

JS Unit Testing

By Riza Nugraha

JS Unit Testing

  • 1,611