Wednesday, June 26, 2013

Jasmine Jquery Spies and Mocks

In the previous post we have seen how to get started with Jasmine by testing a simple function that validates an email address.
But most of the time we want to test JS that runs inside the browser and interacts with the DOM.
We also want to test Ajax calls and mocking the output from the server, which of course is not running during the tests.

You will usually interact with the DOM using some library like JQuery.
Using Jasmine-JQuery you can integrate it easily with Jasmine.
For mocking server response instead Jasmine Spies comes in very handy.

Suppose we keep the same folder structure as in the previous example.
Of course we need to download JQuery and JQuery-Jasmine, place them respectively under src and lib, finally add the following 2 lines to the head section in SpecRunner.html after the jasmine imports:


  1. <script type="text/javascript" src="lib/jasmine-jquery.js"></script>  
  2. <script type="text/javascript" src="src/jquery-1.5.1.min.js"></script>  
So let's test a JQuery plugin which downloads from the server an html snippet containing some list of friends and set it as Html of each element in the JQuery's wrapped set of elements.

  1. $(function(){  
  2.   jQuery.fn.addFriendList = function(){  
  3.     var that = this;  
  4.     $.ajax({  
  5.       url:          'friends',  
  6.       type:         'GET',  
  7.       contentType:  'application/json',  
  8.       dataType:     'json',  
  9.       success: function(html){  
  10.               $(that).html(html);  
  11.            }  
  12.     });  
  13.   }  
  14. });  
This code goes inside src/demo.js, while the test has to be placed inside spec/demo_spec.js

  1. describe("Download friends"function() {  
  2.   
  3.   it("should get the list of friends and insert it in the page",function(){  
  4.     setFixtures('<div id="friends"></container>');  
  5.     spyOn(jQuery,'ajax');  
  6.     $('#friends').addFriendList();  
  7.     jQuery.ajax.mostRecentCall.args[0].success('<ul><li>Arturo</li><li>Concetto</li></ul>');  
  8.     expect($('#friends li').length).toBe(2);  
  9.   });  
  10.   
  11. });  

So let's see step by step what we is happening here:
  • first we define our test suite by calling describe()
  • a call to it() define our test which "should get the list of friends and insert it in the page"
  • setFixtures() comes from Jasmine-JQuery and allows you to insert html into the SpecRunner's DOM. While setFeatures() takes as input a string, loadFixtures() load the html from an external file whose path has passed to loadFixtures(). The default path is spec/javascript/fixtures. But since our test is very simple I preferred using the inline version and to just add a div to the page
  • spyOn(jQuery,'ajax') allows us to "spy" on any call to ajax()
  • $('#friends').addFriendList() just calls our plugin
  • mostRecentCall is one the fields in jasmine.Spy, it gives access to the last call to the spied method. In this case we are calling the success callback with a mocked html.
  • finally we check the div has been modified correctly. Actually a lot of expectations come with Jasmine-JQuery but in this case I preferred not to use any

Spies are in my opinion the greatest feature in Jasmine, the following functions facilitate mocking:
  • andCallFake(fakeFunction), andReturn(), andThrow() basically allows you to determine the output from a call to the spied function
  • andCallThrough() calls the original implementation and it is useful if you want to just verify functions calls

It is indeed possible to verify function calls using the following matchers:
  • toHaveBeenCalled() and toHaveBeenCalledWith() verifies a function has been called (and with what parameters)
  • wasNotCalled() and wasNotCalledWith() verifies a function has not been called (and with what parameters)

One last feature I have not covered in these posts are Asynchronous Specs, which come handy if you want to simulate different asynchronous events occurring in different order.

Jasmine Unit Testing Javascript



Unit testing becomes even more important when you program in a scripting language like Javascript, since you do not have the compiler rescueing you at least from syntactic mistakes.

After playing a little bit with Ruby I could not resist from falling in love with RSpec and its incredible expressivity! So the day I came across Jasmine I had no doubt about what to use to test my JS!

As it says in the introduction the design of Jasmine has been inspired by RSpec but also by other valid alternatives like JSpec.

Jasmine comes in different flavors: stand-alone, Ruby Gem, Node.js module and even Maven plugin! We are going to use the stand-alone version for now. You can download it from http://pivotal.github.com/jasmine/download.html

Downloaded one of the zip, and unpacked in some folder you will find a file called SpecRunner.html. This is the file containing everything you need to setup Jasmine and get started with testing.
Let's have a closer look:


  1. <link rel="stylesheet" type="text/css" href="lib/jasmine-1.0.0.rc1/jasmine.css">  
  2. <script type="text/javascript" src="lib/jasmine-1.0.0.rc1/jasmine.js"></script>  
  3. <script type="text/javascript" src="lib/jasmine-1.0.0.rc1/jasmine-html.js"></script>  
This basically imports Jasmine itself with relative css to make the result's report looking prettier :)

Then we find the files containing the code we want to test:

  1. <script type="text/javascript" src="src/Player.js"></script>  
  2. <script type="text/javascript" src="src/Song.js"></script>  
And finally the tests are imported

  1. <script type="text/javascript" src="spec/SpecHelper.js"></script>  
  2. <script type="text/javascript" src="spec/PlayerSpec.js"></script>  
Following the head section, in the body you will just find few lines, responsible to actually run the rest

  1. <script type="text/javascript">  
  2.   jasmine.getEnv().addReporter(new jasmine.TrivialReporter());  
  3.   jasmine.getEnv().execute();  
  4. </script>  
To create a suite of tests you basically need 2 functions:
- describe(): groups tests together providing also setup and teardown hooks
- it(): contains the test itself

Both of them take as first parameter a string, used as a description of what is going to be tested (for describe() ) and what is the expected outcome (for it() ). This is something very familiar to the RSpec's users and it makes it much easier to analyze the results of the tests once they have run.

Other important features are expectations, equivalent to asserts in JUnit.

So let's write a simple unit test for a function validating an email address. According to Test Driven Development let's write the tests first.
Edit a file under the spec folder called demo_spec.js, with:

  1. describe("Email validation"function() {  
  2.   
  3.   it("should validate someone@somewhere.com",function(){  
  4. console.log("aaa");  
  5.     var result = is_valid("someone@somewhere.com");  
  6.     expect(result).toBe(true);  
  7.   });  
  8.   
  9.   it("should not validate someone@somewhere",function(){  
  10.     var result = is_valid("someone@somewhere");  
  11.     expect(result).not.toBe(true);  
  12.   });  
  13.   
  14. });  
As you can see I used one of the built in expectations called toBe() with the relative not.toBe(), which is the logical not.

Let's now write the is_valid function in demo.js, which will be placed under src:

  1. is_valid = function(email){  
  2.   var reg_expr = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;  
  3.   return reg_expr.test(email);  
  4. };  
To run the test we also need to modify the SpecRunner.html, so that the head section will look like this:

  1. <title>Jasmine Test Runner</title>  
  2.   <link rel="stylesheet" type="text/css" href="lib/jasmine-1.0.0.rc1/jasmine.css">  
  3.   <script type="text/javascript" src="lib/jasmine-1.0.0.rc1/jasmine.js"></script>  
  4.   <script type="text/javascript" src="lib/jasmine-1.0.0.rc1/jasmine-html.js"></script>  
  5.   <script type="text/javascript" src="src/demo.js"></script>  
  6.   <script type="text/javascript" src="spec/demo_spec.js"></script>  
And finally load SpecRunner in the browser and enjoy the results:




Just for curiosity let's change the second test to:

  1. it("should not validate someone@somewhere",function(){  
  2.     var result = is_valid("someone@somewhere");  
  3.     expect(result).toBe(true);  
  4.   });  
It should now fail:







And in fact it does!