Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / All-Topics

Getting Started With Jasmine

5.00/5 (4 votes)
15 Feb 2015CPOL4 min read 10.7K  
Getting started with Jasmine

jasmine

A couple of weeks ago, in a post about SQL unit testing, I mentioned that there are now several feature-rich JavaScript unit testing frameworks available. Perhaps the most popular, given its association with Angular, is Jasmine.

Jasmine’s main selling points (although it is free!), are described on its website as follows:

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.

I have found the transition to writing JavaScript unit tests with Jasmine relatively painless, and if you have experienced writing unit tests in a server-side language and you are planning to begin unit testing your JavaScript, you shouldn’t have any problems getting started.

Each of the following features of server-side unit testing frameworks, such as NUnit and JUnit, has an equivalent capability in Jasmine:

  • Grouping related tests
  • Assertions
  • Setup and Teardown

In addition to these essential features, Jasmine includes mocking functionality, which has some similarities to server-side mocking frameworks such as Moq.

What Does a Jasmine Unit Test Look Like?

Whereas a tSQLt unit test is a stored procedure, and a NUnit test is a method, a Jasmine unit test is a function. To be more specific, a Jasmine unit test (or a ‘spec’ in Jasmine lingo) is defined by a call to the it function. The it function takes two parameters: a string which represents the name of the test, and a function which defines the test itself. Here is a very simple example (all examples are on this plunker):

JavaScript
// Application code
function getManagerName(){
   return 'Roberto Martinez';
}
 
// Tests
describe("getManagerName tests", function() {
   it('returns correct value', function(){
      var name = getManagerName();
      expect(name).toBe('Roberto Martinez');
   });
});
JavaScript
// Application code
function getManagerName(){
   return 'Roberto Martinez';
}

// Tests
describe("getManagerName tests", function() {
   it('returns correct value', function(){
      var name = getManagerName();
      expect(name).toBe('Roberto Martinez');
   });
});

In this example, you can see that Jasmine tests follow the normal unit testing process: call a function, then perform an assertion to verify that the function behaves as expected. You will also see that the it function call lives inside of a call to the describe function.

We run our Jasmine tests by creating an empty html page in which we reference the JavaScript we are testing, our tests, the Jasmine library, and some CSS included in the Jasmine download (for instructions on downloading and running tests, visit the website). When this page loads, it automatically runs the tests, and displays the results in a nice-looking list, like this:

jasmine-screenshot

Grouping Related Tests

In a C# or Java application, we typically group related tests together in a single test class. For example, we may write a test class for each class in our application that we wish to test, or we may have a separate test class for each method we wish to test.

In Jasmine, we group related tests together using the describe function. This function is used to create a test suite, that is, a collection of related tests. In our example above, our suite is given the name getManagerName. A nice feature of Jasmine is that we can nest test suites, so for example, one describe function call which contains all tests for a particular feature, can then contain a nested describe function call for each of its individual functions.

Let’s build on our example to demonstrate this. Below we have an object literal which contains a number of functions. We will group all our tests for this object into a suite named Everton Object, and within this suite we will define sub-suites for each of its functions.

JavaScript
// Application code
var evertonObject = {
   getManagerName: function(){
      return 'Roberto Martinez';
   },
   getYearFounded: function(){
      return 1878;
   },
   getKitColor: function(kitType){
      if(kitType==='home')
         return 'blue';
      else if(kitType==='away')
         return 'black';
      else
         throw 'unknown kit type';
   }
};
 
// Tests
describe("Everton Object", function() {
   describe("getManagerName", function() {
      // First getManagerName test
      it('returns correct value', function(){
         var name = evertonObject.getManagerName();
         expect(name).toBe('Roberto Martinez');
      });
 
      // More getManagerName tests
   });
 
   describe("getYearFounded", function() {
      // getYearFounded tests
   });
 
   describe("getKitColor", function() { 
      // getKitColor tests
   });
});
JavaScript
// Application code
var evertonObject = {
   getManagerName: function(){
      return 'Roberto Martinez';
   },
   getYearFounded: function(){
      return 1878;
   },
   getKitColor: function(kitType){
      if(kitType==='home')
         return 'blue';
      else if(kitType==='away')
         return 'black';
      else
         throw 'unknown kit type';
   }
};

// Tests
describe("Everton Object", function() {
   describe("getManagerName", function() {
      // First getManagerName test
      it('returns correct value', function(){
         var name = evertonObject.getManagerName();
         expect(name).toBe('Roberto Martinez');
      });

      // More getManagerName tests
   });

   describe("getYearFounded", function() {
      // getYearFounded tests
   });

   describe("getKitColor", function() { 
      // getKitColor tests
   });
});

Assertions

We verify behaviour in Jasmine by calling the expect function to assert that some condition is true. The expect function follows the typical Assert pattern found in other testing frameworks, where we can compare values, but also can check whether or not objects exist, if exceptions have been thrown, if variables are “truthy”, and many other verifications.

Let's add some tests for a getKitColor function, including a verification that an exception is thrown when appropriate.

JavaScript
// Application code
var evertonObject = {
   getManagerName: function(){
      return 'Roberto Martinez';
   },
   getYearFounded: function(){
      return 1878;
   },
   getKitColor: function(kitType){
      if(kitType==='home')
          return 'blue';
      else if(kitType==='away')
         return 'black';
      else
         throw 'unknown kit type';
   }
};
 
// Tests
describe("Everton Object", function() {
 
   describe("getKitColor", function() {
      it('returns blue for home', function(){
         var color = evertonObject.getKitColor('home');
         expect(color).toBe('blue');
      });
 
      it('returns black for away', function(){
         var color = evertonObject.getKitColor('away');
         expect(color).toBe('black');
      });
 
      it('throws exception for unknown kit type', function(){
         var unknownKitType = function(){
            evertonObject.getKitColor('good afternoon');
         };
         expect(unknownKitType).toThrow('unknown kit type');
      });
   });
);
JavaScript
// Application code
var evertonObject = {
   getManagerName: function(){
      return 'Roberto Martinez';
   },
   getYearFounded: function(){
      return 1878;
   },
   getKitColor: function(kitType){
      if(kitType==='home')
          return 'blue';
      else if(kitType==='away')
         return 'black';
      else
         throw 'unknown kit type';
   }
};

// Tests
describe("Everton Object", function() {

   describe("getKitColor", function() {
      it('returns blue for home', function(){
         var color = evertonObject.getKitColor('home');
         expect(color).toBe('blue');
      });

      it('returns black for away', function(){
         var color = evertonObject.getKitColor('away');
         expect(color).toBe('black');
      });
 
      it('throws exception for unknown kit type', function(){
         var unknownKitType = function(){
            evertonObject.getKitColor('good afternoon');
         };
         expect(unknownKitType).toThrow('unknown kit type');
      });
   });
);

Setup and Teardown

Setup and Teardown functions can be implemented in Jasmine by calling the beforeEach, afterEach, beforeAll and afterAll functions. These behave as you would expect.

JavaScript
describe("Everton Object", function() {
 
   beforeEach(function() {
      // do this before each test
   });
 
   afterEach(function() {
      // do this after each test
   });
 
   // Tests...
});
JavaScript
describe("Everton Object", function() {

   beforeEach(function() {
      // do this before each test
   });

   afterEach(function() {
      // do this after each test
   });

   // Tests...
});

Mocking

Jasmine uses spies to mock functions and therefore help us isolate pieces of code for testing. Spies let us easily count how many times a function is called, specify a return value, or define an alternative implementation for a function. We need to call the spyOn function to specify which functions we wish to track or mock.

Let’s add a getLeaguePosition function which calls a getPosition function on a separate object. Assume that this called function is quite complicated, and we therefore wish to mock it in order to isolate the function on evertonObject. We will check that it is called, and also that the value it returns is returned by the function we are testing.

JavaScript
// Application code
var evertonObject = {
   //..other stuff
   getLeaguePosition: function(){
      return leagueTable.getPosition();
   }
};
 
var leagueTable = {
   getPosition: function(teamName){
      // some complicated logic in here which calculates league position
      return 234;
   }
};
 
// Tests
describe("getLeaguePosition", function(){
   beforeEach(function() {
      spyOn(leagueTable, 'getPosition').and.returnValue(12);
   });
 
   it('calls leagueTable.getPosition', function(){
      evertonObject.getLeaguePosition();
      expect(leagueTable.getPosition).toHaveBeenCalled();
   });
 
   it('returns value from leagueTable.getPosition', function(){
      var position = evertonObject.getLeaguePosition();
      expect(position).toBe(12);
   });
});
JavaScript
// Application code
var evertonObject = {
   //..other stuff
   getLeaguePosition: function(){
      return leagueTable.getPosition();
   }
};

var leagueTable = {
   getPosition: function(teamName){
      // some complicated logic in here which calculates league position
      return 234;
   }
};

// Tests
describe("getLeaguePosition", function(){
   beforeEach(function() {
      spyOn(leagueTable, 'getPosition').and.returnValue(12);
   });

   it('calls leagueTable.getPosition', function(){
      evertonObject.getLeaguePosition();
      expect(leagueTable.getPosition).toHaveBeenCalled();
   });

   it('returns value from leagueTable.getPosition', function(){
      var position = evertonObject.getLeaguePosition();
      expect(position).toBe(12);
   });
});

Disadvantages of Using Jasmine

It would seem fair to quickly outline some possible disadvantages of using Jasmine before finishing. Its main competitors in the JavaScript unit testing space are Mocha and QUnit. This article compares the three and suggests that testing asynchronous code can be troublesome, compared to Mocha. I am more than happy with Jasmine so far, although I am yet to test it with asynchronous requirements.

All examples here can be viewed and played with on this plunker.

The post Getting Started With Jasmine appeared first on The Proactive Programmer.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)