Unit Testing with Jasmine

What we will cover

  • Why Jasmine
  • Project setup
  • Make code testable
  • Write test cases
  • Maintainence on code
Documentation Home

Jasmine is a behavior-driven development framework for testing JavaScript code with no dependencies, and can run in Node or browser.

In a BDD style test you describe your code and tell the test what it should be doing. Then you expect your code to do something.


            describe ( 'presentation.js' , function(){
              //what it should do
              it ( 'should be informative', function(){
                  //expect something 
                  expect( 
                      presentation.inform() 
                  ).toBeTruthy();
              )};
            } );
          

Setting up Jasmine

  1. Download Jasmine standalone package.
  2. Unzip it into a folder into project
  3. Add your source files to SpecRunner.html
  4. Create spec files and add them to SpecRunner.html
  5. Open SpecRunner.html in a browser.

Folder Structure


            jasmine
            |--lib
            |  |--jasmine.js
            |  |--jasmine-jquery.js
            |--spec
            |  |--someSpec.js
            SpecRunner.html
          

Source file would be under js folder


            //...following the default script
            
              
            //source code
            
            //spec files, test cases
            
            
            
          

Matchers


            toBe( 'expected' )        //exact compare (===)
            toEqual( 'expected' )     //more general compare, can compare objects
            toBeDefined( )            //checks if var is not undefined
            toMatch( /regex/ )        //matches against regex
            toBeTruthy( )             //checks if var is truthy
            toBeLessThan( number )    //checks if value is less than number
          

Any matcher can be "reversed" by including the not keyword.

expect( 5 ).not.toEqual( 3 );

jasmine-jquery

A set of matchers and functions that help you test DOM elements


            toBeInDOM()
            toBeVisible()
            toContainHtml(string)
            toHaveClass(className)
          

Spies

Spies are utilities provided by jasmine to monitor function calls.

A spy can stub any function and tracks calls to it and all arguments.

use jasmine.createSpy() with jasmine.clock() jasmine.ajax() to handle asynchronous event

To use it we tell Jasmine to spyOn something. By default they are similar to mocks in other unit testing frameworks.


              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');
                });
              }
            

Using a spy


              describe("A spy", function() {
                ...

                it("tracks that the spy was called", function() {
                  expect(foo.setBar).toHaveBeenCalled();
                });
              }
            

Spy matchers

validates that the method was called


            expect( myObj.showOtherDiv ).toHaveBeenCalled();
          

validates that the method was called with specific arguments


            expect( myObj.showOtherDiv ).toHaveBeenCalledWith( args );
          

Organizing our code

Avoid these situations:

A general lack of structure; almost everything happens in one big function, and a lot of anonymous functions.

Complex functions: if a function is doing too much.
Hidden or shared state: some important state or varible we need to test on but hidden in anonymous functions.

An example of not testable code


            $(document).ready(function(){
              var div1 = $('div.one'),
                  itemCount = getItemCount( 'li.item' );

              div1.on('click',function(){
                ...
              });

              div1.on('change', function(){
                ...
              });

              ...

              function getItemCount( selector ){
                return $( selector ).length;
              }
            });
          

Why not testable

  • All the code is hidden in the $(document).ready() closure.
  • The event handler is anonymous function passed directly into the on function.
  • itemCount is hidden in closure.

Make it more testable


                $().ready(function(){
                  //a bunch of codes
                  //doing first task
                  //doing second task
                })
              

                function init() {
                  do1();
                  do2();
                  ...
                }
                
                function do1 () {}
                function do2 () {}
                ...
              

                div1.on('click',function(){
                   ...
                });
              

                function handleClick () {}

                div1.on('click', handleClick);
              

                function do1() {
                  var someImportantVariable = value;
                }
              

                var app = {
                  someImportantVariable = value,
                  ...
                }
              

                var app = {
                  init : function(){
                    div1.on('click', this.handleDivClick);
                    div1.on('change', this.handleDivChange);
                    ...
                    this.itemCount = 
                        this.getItemCount( 'li.item' );
                  },

                  itemCount: 0,

                  getItemCount : function( selector ){
                    return $( selector ).length;
                  },

                  handleDivClick : function( e ){ ... }

                  handleDivChange : function() { ...}

                  ...
                };
              

Good Practices on Writing test cases

General guidelines

  • Lightweight
  • Repeatable
  • Fast
  • Consistent
  • Easy to write and read

Make each test isolated and independent to all the others

Any given behaviour should be specified in one and only one test

The execution/order of execution of one test cannot affect the others

Each test should only test one code unit

Don't test multiple concerns in the same test


              it('should properly validate and update error message', function(){...})
              
              it('should properly valudate', function(){...})
              it('should update error message', function(){...})
            

Setup properly the actions that apply to all the tests involved


              beforeEach, afterEach, beforeAll, afterAll
            

Name unit tests clearly and consistently

All the describe and it should be informative

Maintainence

  • Always run regression test when refactoring code
  • Add unit test when adding new functionality
  • Create new tests for every defect

References

https://github.com/mawrkus/js-unit-testing-guide