JP's blog

Tech talk with a french twist.

DRYing Up Your JavaScript Jasmine Tests With the Data Provider Pattern

Lately, I’ve been using Jasmine to unit test my JavaScript code. Jasmine is a great BDD framework. It has a very clean and easy syntax. It has a lot of useful features such as matchers/expectations and mock objects (spies).

However, one feature I have been missing is a data provider mechanism. I learned about that feature while using PHPUnit for PHP unit testing. The basic premise of a data provider is to feed a test with multiple test values to avoid repeating your test code over and over.

In this post, I will show you how you can leverage that data provider pattern with Jasmine.

The problem

First, let me explain the problem I am trying to solve. Imagine you have some validation code that must verify a username. A correct username matches the following requirement:

  • Must be alphanumeric or underscore
  • Minimum of 3 characters
  • No more than 12 characters

Positive and negative test cases could look like this:

Username validation with Jasmine
1
2
3
4
5
6
7
8
9
10
11
12
13
14
describe("username validation", function() {
  it("should return true for valid usernames", function() {
    expect(validateUserName("abc")).toBeTruthy();
    expect(validateUserName("longusername")).toBeTruthy();
    expect(validateUserName("john_doe")).toBeTruthy();
  })

  it("should return false for invalid usernames", function() {
    expect(validateUserName("ab")).toBeFalsy();
    expect(validateUserName("name_too_long")).toBeFalsy();
    expect(validateUserName("no spaces")).toBeFalsy();
    expect(validateUserName("inv*alid")).toBeFalsy();
  })
})

The above test is pretty straightforward: we are testing a method called validateUserName and we are making sure that it returns true for valid values and false for invalid ones.

The problem I have with the above code is the repetition of the same expectation in each of the two it blocks.

DRYing it up

Let’s look at the same 2 tests using the data provider pattern. I came up with the following syntax:

Same validation without repetition
1
2
3
4
5
6
7
8
9
10
11
12
13
describe("username validation", function() {
  using("valid values", ["abc", "longusername", "john_doe"], function(value){
    it("should return true for valid usernames", function() {
      expect(validateUserName(value)).toBeTruthy();
    })
  })

  using("invalid values", ["ab", "name_too_long", "no spaces", "inv*alid"], function(value){
    it("should return false for invalid usernames", function() {
      expect(validateUserName(value)).toBeFalsy();
    })
  })
})

Basically, I wrap each it block with a using block. The using block specifies what values to pass to the it test.

The using function is really easy to implement:

Data provider code for Jasmine
1
2
3
4
5
6
7
8
9
function using(name, values, func){
  for (var i = 0, count = values.length; i < count; i++) {
    if (Object.prototype.toString.call(values[i]) !== '[object Array]') {
        values[i] = [values[i]];
    }
    func.apply(this, values[i]);
    jasmine.currentEnv_.currentSpec.description += ' (with "' + name + '" using ' + values[i].join(', ') + ')';
  }
}

The using function takes 3 arguments:

  • name: that is the name describing the set of values
  • values: array of values to use for the tests (note that each element in the array can be a simple value or an array of multiple values)
  • func: function wrapping the it block

The last line enhances the test description with the name associated with the values (first argument of the using block). It also adds which value has been used for a given test. This will make troubleshooting easier in case of a test failure.

Demo

A demo is usually better for understanding than a long explanation with code samples so I pushed a demo to my GitHub account:

Here is a screenshot of the Jasmine output:

Wrap up

I did some research prior to setting this up and I could not find an existing solution. The data provider functionality is standalone and can easily be added to the spec helper file in your project.

What do you think? Do you find this technique useful? What other tips do you use for DRYing up your tests? If you have feedback on this post, feel free to comment below or to contact me via Twitter (@jphpsf).

Possibly related posts

Comments