Writing Your Own Tests with Jasmine

Reading time ~6 minutes

Test driven development (or TDD), is a JavaScript development paradigm that espouses not only building tests to make sure your code works properly, but actually writing those tests before you code. The workflow looks something like this:

  1. Conceptualize project.
  2. Build tests for projects.
  3. Watch tests fail.
  4. Make tests not fail.
  5. Improve project.
  6. Repeat.

In this paradigm, the tests essentially serve as a rough outline for your code. Writing them first forces you to really think through what it is you want your code to do, and how it is you will actually do it. It can be a heady process, but once you are done, the benefits are self-evident. Afterwards, you just code to the test, filling in each piece as needed to make tests pass. And later when you are adding features, you will instantly know that you broke your code.

So how can we make writing tests as painless as possible? Thankfully are a variety of testing libraries out there to help simplify and streamline the process. Today, we’re going to talk about one called Jasmine.

Getting Started

As you might expect, Jasmine has an npm module which you can install easily enough through your command line:

npm install -g jasmine

Afterwards, you’ll want to head to your project’s directory and type:

jasmine init

And if you’d like you can even provision yourself with some example tests using:

jasmine examples

Finally, if you’d like to configure your project’s testing suite at all, you fill find a config file in spec/support/jasmine.json, and you can actually run your tests by going to your project’s root directory and typing:

jasmine

Writing a Test

Jasmine tests are built on fairly simple syntax, designed to look more or less like english. Well, english with a lot of extra dots and anoymous functions.

describe('Arithmatic', function() {
	
});

Say hello to the describe block. This is the broadest unit of Jasmine tests. It designates a collection of tests all pertaining to a particular part of your code. There is no hard and fast rule about how much of your code any given describe block should cover, just think of it as a sub-heading to help organize your tests.

describe('Arithmatic', function() {
	
  it('should be able to add', function() {
		
  });
	
});

If describe is a sub-heading, than the it block is a bullet point. These are used for each individual nuggets of functionality you want to test. What do you want your code to do? List each thing you come up with in an it block. You’ll notice that the code for each of these is very similar. They are functions which take two parameters, a string description, followed by an anonymous function that contains what will actually be run.

describe('Arithmatic', function() {
	
  it('should be able to add', function() {
    expect(2+2).toEqual(4);
  });
	
});

And now with an expect statement, our test is complete! If an it block represents the individual qualities being tested, the expect statements are the actual stress tests we are subjecting those qualities to. If any expect statement were to fail in testing, the enclosing it block will be show up as failed (and even print a convenient explanation for the failure). Specifically, expect statements work by comparing the argument passed in the expect call to the argumant passed in the “matcher”, in this case toEqual. There are a variety of matchers (described below), and they are all designed to read like plain english.

describe('Arithmatic', function() {
	
  it('should be a word', function() {
    expect(typeof 'Arithmatic').toBe('string');
  });
	
  describe('Addition, function() {
		
    it('should be able to add', function() {
      expect(2+2).toEqual(4);
      expect(4+4).toEqual(8);
      expect(2+2).not.toEqual(5);
    });
	
  });
	
});

Note that there is a fair amount of freedom to how you mix and match your describe, it, and expect statements. describe blocks can contain other describe blocks, and it blocks can be nested at the same level as those describes. But you would never place a describe block within an it block, and an it block must be contrained within at least one describe. Additionally, you can stack as many expect statements as you like within an it. Every expect must succeed for the test to pass.

Before and After

Frequently you will want to execute code before and/or after each of your tests. Rather than copying and pasting that code within every it block, Jasmine provides you with the beforeEach and afterEach functions:

describe('Arithmatic', function() {
  var add = function(x, y) {
    return x + y;
  };
  var operands;
	
  beforeEach(function() {
    operands = [1, 2, 3, 4];
  });
	
  it('should be able to add', function() {
    for (var i = 0; i < operands.length; i++) {
      expect(add(operands[i], 2)).toEqual(i+3);
    }
  });
	
  afterEach(function(){
    operands = [];
  });
	
});

Here we are working with some functions and variables that we’re going to use in our tests, and we use beforeEach to ensure they are set properly before each test. afterEach on the other hand, will allow us to reset any changes we may have made during the test. You do not need both of these blocks, and would probably only use one in most cases, but both are available. Note that unlike describe and it, neither takes a descriptive string as an argument, just the anonymous function. Also note that all of JavaScript’s regular scoping rules apply. If you declare a variable in your beforeEach block it will not be available inside your it blocks. Instead, you must declare variables within the enclosing describe and modify them as needed within beforeEach.

Some Useful Matchers

As I said earlier, toEqual is called a “matcher”, and there are a few others you can choose from. You can even write your own. In the mean time, here are some others you might find useful:

expect('a').not.toEqual('b');

Using not before your matcher will do just what you expect: expect things not to match.

expect('b').toBe('b');

toBe is very similar to toEqual, but is much stricter when it comes to objects. toEqual will actually recursively examine two different objects to see if they are “equivalent”. For example expect({a: 1}).toEqual({a: 1}) will pass, but expect({a: 1}).toBe({a: 1}) will fail. Although the two objects are equivalent, they do not actually refer to the same spot in memory and so are not strictly equal. For primitive data types there is no difference between toBe and toEqual.

expect(obj.prop).toBeDefined();

expect(obj.foobar).toBeUndefined();

As you might expect, toBeDefined and toBeUndefined test whether or not a thing is undefined.

expect(1).toBeTruthy();

expect(0).toBeFalsey();

Also pretty straightforward. Tests whether the given argument is truthy or falsey.

expect([1, 2, 3]).toContain(2);

toContain is a nifty little matcher, which will check an array or string to see if it contains a given item or substring.

expect(3.14).toBeCloseTo(3.1, 1);

toBeCloseTo allows you to round numbers to a specified decimal place. The above example passes becasue 3.14 equals 3.1 when rounded to the first decimal place. In contrast, expect(3.14).toBeCloseTo(3.1, 2) would fail. Using a zero (i.e. toBeCloseTo(n, 0)) will effectively round your arguments to whole integers, while negative numbers start looking at the tens places to the left of the decimal: expect(120).toBeCloseTo(100, -2).

There are a handful of other matchers out there as well, which you can find explanations of in Jasmine’s documentation, including toBeNull, toBeGreaterThan, toBeLessThan, toThrow (for exceptions), and toMatch (for regex, i.e. regular expressions).

Now Go Write Some Tests!

Test driven development is the basis for smart, focused, effecient coding. It allows you to spend less time wondering what to do next and helps you avoid project-breaking bugs before they begin. All the tools you need to build these tests are easy and available. So what you waiting for?

Scraping the Web for Fun and Profit

So you and your team have been talking about your new social web app. Development has been going well, and someone had the bright idea: "...… Continue reading

The How's Why's and WTF's of Mongoose Hooks

Published on January 30, 2016