At Rainforest, we love testing as it is the best way to ship high quality software. In addition to using Rainforest tests, we use a bunch of testing tools for our code and one is Jasmine, to test our Backbone.js frontend app.

Jasmine was built by our friends at Pivotal Labs. In this post I’ll show you how to test some basic JS code with Jasmine.

What is testing?

While this post is not an introduction to testing, testing is all about verifying that your code does what it is expected to do with automated tools.

Basic example

Let’s take a basic evenOrOdd function returning you “even” if the value is even, and “odd” if it’s odd:

javascript var evenOrOdd = function(value) { if(value % 2 == 0){ return 'even'; } else { return 'odd'; } };

How can we verify that this code works as expected? We will take a Test-driven development (TDD) approach to this, so we will start with no code at all.

This is the content of our EvenOrOdd.js file right now:

javascript // nothing here, yet.

First, we need a test suite. A test suite for Jasmine starts with a call to the describe function which takes two parameters: a description (string) and a function.

EvenOrOddSpec.js content:

describe("evenOrOdd", function() { });

This is our test suite for evenOrOdd.

The second parameter will include our tests. Let’s start with our first test. A test is made of a call to the it function, taking two parameters: a description (string) and a function. Inside the function, we add an expectation. Let’s see how that looks like:

describe("evenOrOdd", function() { it("returns 'odd' when the number is odd", function(){ expect(evenOrOdd(1)).toEqual('odd'); }); });

The previous example is calling our evenOrOdd function with “1” as the parameter and Jasmine is expecting the return value to be “odd”.

Running the tests give us this result right now:

We are getting this because evenOrOdd is not even defined, let’s do this:

var evenOrOdd = function(){

};

What would happen if we were to run the tests again?

We had a different result, slowly bringing us closer to what we want.

In the TDD world, there is a thing we call “red green refactor” which means the first step is write a test that fails due to the not behaving as expected (or not being present). The green part, is writing the minimum to make that test pass. Finally, the refactor part, is making the appropriate change to have nice and clean code doing what it should be doing.

We did the “red” part of it, let’s make it pass:

var evenOrOdd = function(){ return 'odd'; };

This test will pass:

Screenshot of the passing test

Well, that’s cool, but it’s not exactly the end result we want, so let’s expand our test suite

describe("evenOrOdd", function() {
 it("returns 'odd' when the number is odd", function(){ expect(evenOrOdd(1)).toEqual('odd');
});
it("returns 'even' when the number is even", function(){
 expect(evenOrOdd(2)).toEqual('even'); });
});

Running our tests will now have one failure, and one success:

Well, that is expected as our code is not appropriate, so let’s fix it:

javascript var evenOrOdd = function(value) { if(value % 2 == 0){ return 'even'; } else { return 'odd'; } };

Now our code works as we want, it is covered by some tests. Obviously, this is very basic and non-realitisc code, but that gives you a rough idea.

Click here to go to the repo with the code and tests from this post.

For this example, I only used toEqual, but there are other “matchers” you can use for your tests, here are a few more:

expect(a).toContain("bar"); expect(a.bar).toBeUndefined(); expect(foo).toEqual(1); expect(foo).toBe(true);

You can see a longer list in the official documentation.

I am going to show you a few more things that will make you more efficient at using Jasmine to test your JavaScript.

Nested describe blocks

In previous examples, I showed you a single describe block with a few it blocks with assertions – but you can also nest the describe blocks. You can have a root describe block with nested describe block. A good example is to have one for the main thing you are testing, then sub-describe blocks for the methods.

Given this object that we would test:

var myNumberHelper = { isEven: function(value) {
 // does some magic
}, isOdd: function(value){
 // also does some magic
}};

In this example, I create a describe per method of the myNumberHelper object, giving me a nicely organized feel:

describe("myNumberHelper", function() {
 describe("isEven", function(){
   // some it blocks here to assert is Even is doing its job
 });
 describe("isOdd", function(){
   // some it blocks here to assert isOdd is doing its job
 });
});

Further organization based on context

Whilst you can have many levels of nesting, I strong suggest you try to stay within these 3 levels of nesting. The best practice levels are:

  • Level 1: The object / thing you are testing
  • Level 2: The methods of the object / thing
  • Level 3: The different contexts you need to test the method with

Jasmine doesn’t have a context block like RSpec does, but I love using describe blocks as equivalents for this. With the right description, you can use them to express the current context of the nested tests in the same way.

If we build on our previous examples, here is how it should look:

describe("myNumberHelper", function() {
 describe("isEven", function(){
   describe("when the argument is even", function(){
     it("returns true", function(){
       expect(myNumberHelper.isEven(2)).toBe(true);
     });
   });

describe("when the argument is odd", function(){
     it("returns true", function(){
       expect(myNumberHelper.isEven(1)).toBe(false);
     });
    });
 });

 // ...
});

It feels like we are starting to see a pattern for easy readability and organization of tests, don’t you think?

Setup and teardown

One of the things that is really useful is having a way to re-use setup steps so that you don’t have to repeat yourself – keeping things DRY. Jasmine supports setup steps which are run before each test in a suite. There are also ‘after steps’, which you can use to clean up the state; this is especially useful when the tests share the same context.

You can define those steps into a beforeEach or afterEach:

describe("client", function(){ beforeEach(function(){ this.client = { name: "John Doe", plan: "trial" }; });

it("is on the trial plan", function(){
 expect(this.client.plan).toEqual("trial");
})});

In this example, before each it block, a client is setup. You can also undefine it after each test.

describe("client", function(){ beforeEach(function(){
 this.client = { name: "John Doe", plan: "trial"
}});

afterEach(function(){ this.client = null; });

it("is on the trial plan", function(){
 expect(this.client.plan).toEqual("trial");
})});

These are just some basic examples; I am sure you are already considering a use case for your code base. If you need a hand, ping us on twitter.

Stealthy spies

Jasmine has test double functions that are called spies. A spy can replace any function and help you track its usage, plus the arguments used to call it.

Let’s say you want to make sure you are tracking an action in your analytics software. You can do that easily by using a spy, which will replace your analytics call, whilst ensuring it is being called as expected. How would you do that?

For this example code:

var myNumberHelper = { isEven: function(value){
 var even = value % 2 == 0;
 analytics.track('is_even', value);
 return even;
}, isOdd: function(value){
 // ...
}}l

The test is as simple as this:

describe("myNumberHelper", function() {
 describe("isEven", function(){ // ...

describe("when the argument is even", function(){
   it("sends a 'is_even' event", function(){
     // This is replacing the implementation of analytics.track with a spy
     spyOn(analytics, 'track');     // The action which you expect would trigger a call to this     myNumberHelper.isEven(2);     // Then add an expectation that it has been called

     expect(analytics.track).toHaveBeenCalled();
   });
 });  // ...
});

What did we do exactly?

First, you need to create a spy. This is replacing the implementation of your code, in this case analytics.track, with code that will help Jasmine know if this specific code was called (plus with which arguments if you want).

Great, that’s setup. Next step is to trigger the code you are expecting to call analtyics.track – in this case we’re expecting a call to isEven.

Finally, the expectation itself, which will verify that analytics.track was actually called.

You could have made the expectation even more explicit by specifying the arguments you were expecting by changing it to: expect(analytics.track).toHaveBeenCalledWith(“is_even”, 2).

Spies are pretty useful, and they can be more flexible than this. For example, it can call the original function it’s replacing if you want with spyOn(analytics, ‘track’).and.callThrough();. More on spies can be found in the official documentation.

Disable or mark as pending

Finally, if you want to disable or mark a test as pending you can:

  • prefix the it by a x, giving xit which will make it pending
  • use a pending() inside the it block, which will also make it pending
  • use xdescribre which will disable all the tests nested into it

What does this mean and what’s the difference? In all cases, those tests will be skipped. The difference is that the disabled one won’t be shown in the output, but the pending ones will show up in the results as pending.

What’s next?

Note that there are different ways to integrate Jasmine to run it automatically as part of your backend test suite, or upon saving your code or your tests.

There is much, much more to Jasmine. You should look at the official documentation as it’s really useful; as the project isn’t too big it’s easy to read it all – which I suggest if you’re serious.