News

How good is your JavaScript? Test with Jasmine

Discover how easy it is to ensure code quality by testing your program via the Jasmine unit testing framework

jasmine

Complex applications are like a cube of jelly. Changing anything tends to shake the entire system up, causing all kinds of weird side effects. Unit testing permits you to alleviate this problem by outfitting the product with a variety of self tests. These can then be run automatically, pointing out areas that were negatively affected by a recent change.

In the last few years, a large variety of test systems have entered and left the market. Jasmine is still among the most popular unit testing frameworks for JavaScript, as its functioning does not require the presence of a DOM. This means that you can use it to test both server and client-side JavaScript code. Test cases are written in a domain-specific language that is intended to be easy to read for programmers and non-programmers alike.

A convenient runner website transforms your favourite web browser into a powerful testing console waiting to track and squish bugs in your program code. So, let’s get started!

Download the framework

Jasmine can be downloaded by pointing your browser to pivotal.github.io/jasmine. Scroll to the bottom and click the link to standalone distributions in order to get to a GitHub site offering you various zip files. Select the one for version 2.0.0 in order to use the most current edition of the framework currently available. Click ‘Raw’ to download it.

Test runner?

Jasmine tests usually are run via a file called ‘TestRunner.html’. Open it in an HTML editor of choice in order to add the new test and source files at the positions marked with comments. The initially provided example engine and test case files can and should be removed before adding actual code.

001  <!-- include source files here... -->
002  <script type=”text/javascript” src=”src/Player.js”></ script>
003  <script type=”text/javascript” src=”src/Song.js”></    script>
004  <!-- include spec files here... -->
005  <script type=”text/javascript” src=”spec/SpecHelper.    js”></script>
006  <script type=”text/javascript” src=”spec/PlayerSpec.    js”></script>

Test suites

Unit tests should be grouped up for easier management. This is done by creating suites via the describe function. Its structure follows that of natural language wherever possible; the first parameter explains what must be tested, whereas the second one contains a function that carries out the evaluation.

001  describe(“Name of Suite”, function() 
002  {    
003  //Test code goes here
004  });

The it command

Actual test work is performed in it() functions. Each it function should be set up so as to test one specific aspect of the product. The code snippet below shows a basic comparison that ensures a true value is really a true value.

001      describe(“A suite”, function() {
002    it(“Tests something simple”, function() {
003      expect(true).toBe(true);
004    });
005  });
006 

Code is code

Before running our little example, we must clarify that unit tests are made up of code written completely by the developer of that particular solution. The framework runs the test methods that you provide and uses the results to determine between success and failure. This can be proven by adding a call to alert() to your test case.

Matchers compare…

Unit tests work by comparing the results of the actual computation with the one intended to be returned. Your expectations are passed into the program via an expect() clause, which takes the actual value produced by the routine under test. It can then be compared to something else using a matcher.

001    it(“Tests some numbers”, function() 
002    {
003      expect(1234).toBe(return1234());
004    });
005



…more than one thing

It is permissible to have more than one expect()-Clause in a test case. During evaluation, the first test to fail will not trigger the termination of the entire routine (as it has failed). Most developers expect this behaviour – be aware that Jasmine does not short circuit the evaluation of test code.

Is it something?

The simplest form of comparison works by simply comparing the returned result with the intended value. This can be achieved by using the toBe function, which we first introduced a few steps ago. It fails whenever the two values are not considered equal by a === comparison.

Analyse strings

Regular expressions tend to be very helpful when dealing with textual data. ‘Straight’ comparisons can be done by invoking the toBe function. If the use of a regular expression is desired, toMatch is better suited to the task at hand. The syntax of the RegEx follows JavaScript conventions.

001    it(“Tests whether an array contains an element”,     function() 
002  {    003      var a = [‘a’, ‘b’, ‘c’];
004      expect(a).toContain(‘c’);
005    });
006   

Numbers

Figuring out whether a number is larger or smaller than another arbitrary value is usually achieved via an if clause. This tends to enlarge the length of the unit test. The toBeLessThan and toBeGreaterThan clauses provided in Jasmine allow you to shorten all numeric tests.

001    it(“Tests Jasmines numeric functions”, function()
002  {
003      expect(2).toBeLessThan(3);
004      expect(2).toBeGreaterThan(1);
005    });

Negate matchers

Most matchers can be forced to work in ‘inverted mode’. This means that a matcher will consider its test successful only if the condition it is intended to analyse is not met. For example, putting the ‘.not.’ object in front of toBeTruthy transforms the comparison into a lookalike of toBeFalsy.

001    it(“Tests whether an array contains an element”, function() 
002  {
003      var a = [‘a’, ‘b’, ‘c’];
004      expect(a).not.toContain(‘d’);
005    });
006



Set up code

Sometimes, the engine code must be reset to a predefined state before each of the tests can be run. This is achieved by providing the describe() function with a beforeEach clause. The function passed to beforeEach will be run before each of the it() function payloads.

…and tear it down

Similarly, the afterEach function will be executed after each if() clause was processed. It can be used to clear up leftovers caused by the test execution. Alternatively, you can also use it to reset the engine to a predefined state needed for the next test.

001  describe(“A suite”, function() 
002  {
003    afterEach(function() {
004      window.valueStore = 0;
005    });

Global stuff

The body of the describe function is processed linearly by the JavaScript interpreter. This means that you can use it to perform any action that needs to be handled before the actual unit tests can be run. Our example declares a variable called valueStore before running the first it() clause.

001      describe(“A suite”, function() {
002    var valueStore;
003    it(“Uses valueStore”, function() 
004      {
005



More It functions

The code snippets for the steps shown tend to show more than one it() function per block. This is not a problem. Jasmine permits you to have an almost unlimited number of test cases nested into one describe suite. Simply nest them below one another as shown in the snippet.

001      it(“Tests Jasmines numeric functions”, function()
002      {
003          expect(2).toBeLessThan(3);
004      });
005      it(“Uses the not qualifier”,function()
006      {
007      
008      });

It is not

As code matures, some unit tests become unneeded. Removing them is not particularly smart, as they might be needed again at some point in the future. This problem can be addressed by using the non-working xdescribe and xit functions. Their syntax is an exact match of describe and it.

Harness the spy

Spies allow you to analyse how the code under test interacts with one or more functions. Simply initialise it and invoke the method just as you would do normally. From the moment spyOn was called, the invocations get absorbed by the Jasmine framework, which tracks the calls.

001      it(“Uses a spy”,function()
002      {
003          spyOn(someObject, “functionName”);
004          //Use someObject
005      });

Chained spying

Adding the andCallThrough command to the declaration of the spy enables you to chain the invocations and reduce the amount of impact on the system. This means that Jasmine will analyse the callings of the method, but will then proceed with forwarding them to the actual implementation provided by the system under test.

001  {
002      it(“Uses a spy”,function()
003      {
004          spyOn(someObject, “functionName”).        andCallThrough();
005          //Use someObject
006      });

Was it invoked?

The most basic use case for a spy involves finding out whether the method in question was invoked during the execution of the test case. In our case, the test will be successful only if the code masked by the comment invokes the functionName method at least once.

001  {
002      it(“Uses a spy”,function()
003      {
004          spyOn(someObject, “functionName”).        andCallThrough();
005          //Use someObject
006          expect(someObject.functionName).        toHaveBeenCalled();
007      });
008   

Parameters

Advanced testing requires the use of an artificial spy. These can be generated by invoking the createSpy function with a string identifying the name of the function which you want to be shadowed. The toHaveBeenCalledWith function permits you to determine whether the parameter set in question was actually passed in.

001      it(“Uses an artificial spy”,function()
002      {
003           var whatAmI = jasmine.createSpy(‘whatAmI’);
004           whatAmI(“Welcome”);
005       whatAmI(“I am unreal”);
006           expect(whatAmI).toHaveBeenCalledWith(“Welco    me”);
007      });

How often was it called?

In some cases, the amount of invocations caused during the execution of the test case can be interesting. If an artificial spy is inserted, it can perform an analysis of the call structure. Its results can then be used to determine the number of times the method was called during the execution of the test case.

With return values

Sometimes, modifying the value of a function returned can be very helpful. This can be achieved by adding the andReturn() clarifier to the creation of the spy. From that moment onwards, all invocations of the method managed by the spy will return the value passed in.

Control time

When attempting to debug time-based code, compressing the wait cycles can help to reduce the total execution time of the test battery. Invoking ‘jasmine.clock().install’ halts the flow of time in your program. From then onwards, calling ‘jasmine.clock().tick’ will allow you to advance the time in a helpful step-by-step fashion.

Custom matcher

Even though Jasmine provides you with a large selection of matchers, you might sometimes want to add a new one to the roster. This can be achieved by creating a so-called custom matcher. These are passed in to the framework and can then be used like any other matcher. Look at the code library (left) to see this in action.

Welcome to Node.js

Running your program from the browser can become a nuisance. Fortunately, a Node.js-ready version of the Jasmine framework is available via GitHub under the URL github.com/mhevery/jasmine-node. After having installed it, your tests can be run via the command line of your system. Jasmine-Node even supports the Growl system for sending out notifications through a variety of media.

001    it(“Tests a regex”, function() 
002  {
003      var message = ‘Hello to my readers’;
004      expect(message).toMatch(/my/);
005    });

Is it true?

When it comes to dealing with true and false values, a simple comparison against true and false often is not sufficient enough. toBeTruthy and toBeFalsy can be used to remedy this problem. They work by performing an ‘internal compare’ against a value casted to bool, thereby matching the normal JavaScript behaviour.

Undefined stuff

Generator methods might fail in a way that causes the unit test code to halt with a null reference error. This can be avoided by first checking whether an object is defined or undefined. Use toBeDefined to check for definedness, toBeUndefined checks whether the element does not exist.

Array, taken apart

Figuring out whether an array contains a specific value can be a bit of an intimidating task. Fortunately, Jasmine provides you with the toContain matcher. This checks whether the ‘incoming’ array contains the element required at any position, thereby saving you the effort of creating and maintaining a loop in your unit test.

×