Pragmatic JavaScript Testing with Jasmine

As more and more parts of our applications are written in JavaScript, its important to have them covered with automated tests. Fortunately, there are numerous JavaScript testing tools available. As a BDD fan, the RSpec inspired Jasmine is currently my go-to.

The Basics

For developers coming from RSpec, Jasmine will feel very familiar.

Here’s a simple spec that demonstrates most of the basics:

describe('User', function () {
  beforeEach(function () {
    this.user = new User();
  });

  describe('#age', function () {
    beforeEach(function () {
      this.age = this.user.age;
    });

    it('defaults to 21', function () {
      expect(this.age).toEqual(21);
    });
  });

  describe('#save', function () {
    beforeEach(function () {
      this.age = 30;
      this.user.save({ age: this.age });
    });

    xit('updates itself', function () {
      expect(this.user.age).toEqual(this.age);
    });
  });
});

Use #beforeEach for setup, #describe to group related specs and #it for your examples. The above two examples use the #toEqual matcher. Jasmine comes with basic matchers e.g. #toBe, #toMatch, #toBeNull; check the docs for a complete list. Nested #describe’s are legal but unlike RSpec there’s no #context method.

The last example uses #xit to mark the example as pending. A corresponding #xdescribe also exists to mark a group of specs as pending. These can be useful as a checklist of sorts in a test first workflow.

The abundance of anonymous functions in Jasmine specs can seem a bit strange and verbose but if you’ve done a fair share of JavaScript you’ll quickly get used to it. For those who haven’t just bear with it for now, later I’ll discuss a way to improve the syntax.

Specifying jQuery

With the basics out of the way let’s look at specifying something you’re likely to encounter in every app: jQuery. For jQuery we’ll use the excellent jasmine-jquery library.

jasmine-jquery gives us HTML fixtures and jQuery specific matchers.

Here’s a sample:

describe('thermostat', function () {
  describe('#lossLangage', function () {
    describe('when selecting an inefficient heating temperature', function () {
      loadFixtures('thermostats/show.html');

      beforeEach(function () {
        thermostat.lossLanguage();

        $('#temperature')
          .val('71')
          .change();
      });

      it('warns how much money you could be saving', function () {
        expect($('#warning')).toBeVisible();
      });

      it('includes the inefficient temperature in the warning', function() {
        expect($('#warning')).toHaveText(/71/);
      });
    });
  });
});

This spec is for a #change event handler for a <select> element. #loadFixtures is provided by jasmine-jquery and allows you to use external HTML files for test data.

Here’s the fixture file used above:

thermostats/show.html

<select id="temperature">
  <option>70</option>
  <option>71</option>
  <option>72</option>
  <option>73</option>
  <option>74</option>
  <option>75</option>
</select>

<div id="warning" style="display:none">
</div>

We used two jQuery specific matchers in our examples: #toBeVisible and #toHaveText. These and a lot more are available; be sure to check out the README for the complete list.

If external fixtures files are too heavyweight for your needs jasmine-jquery also provides inline fixtures via #setFixtures.

describe('NewTaskView', function () {
  describe('#render', function () {
    beforeEach(function () {
      setFixtures('<div id="taskForm" />');
      this.newTaskView = new NewTaskView({ model: new Task });

      this.newTaskView.render();
    });

    it('displays a form to enter a new task', function () {
      expect($(this.newTaskView.el)).toContain('form');
    });
  });
});

In this spec our fixture was just a simple <div> element so we decided to define it in the spec. With everything defined in one place it makes the spec easier to understand and follow.

Specifying Ajax

Of course no app is complete without some Ajax. For specifying Ajax we’ll use the excellent jasmine-ajax library. jasmine-ajax monkeypatches jQuery to use a fake XMLHttpRequest object. This fake will store all Ajax requests, allowing you to inspect and even respond to them.

Here’s a example:

describe('EditTaskView', function () {
  describe('#update', function () {
    beforeEach(function () {
      this.task = new Task({ id: 1 });
      this.editTaskView = new EditTaskView({ model: this.task });
      this.editTaskView.render();

      $('form', this.editTaskView.el)
        .find('input[name=description]')
          .val('description')
          .end()
        .submit();
    });

    it('updates its task', function () {
      var request = mostRecentAjaxRequest();
      expect(request.method).toEqual('PUT');
      expect(request.url).toEqual('/tasks/' + this.task.get('id'));
      var params = JSON.parse(request.params);
      expect(params.description).toEqual('description');
    });
  });
});

This example uses jasmine-ajax’s #mostRecentAjaxRequest method to get the last Ajax request. We then specify its method, url and parameters.

Specifying the request is great but only half the battle. We also want to specify our response handlers. Let’s extend the above example with a spec for a failing update:

describe('EditTaskView', function () {
  describe('#update', function () {
    ... // existing successful update spec

    describe('given an invalid task', function () {
      beforeEach(function () {
        this.task = new Task({ id: 1 });
        this.editTaskView = new EditTaskView({ model: this.task });
        this.editTaskView.render();

        $('form', this.editTaskView.el)
          .find('input[name=description]')
            .val('')
            .end()
          .submit();
      });

      it('displays any validation errors', function () {
        var body = {
          description: ["can't be blank"]
        };
        var response = {
          status: 422,
          responseText: JSON.stringify(body)
        };
        var request = mostRecentAjaxRequest();
        request.response(response);
        expect($('#errors', this.editTaskView.el)).toHaveText("Description can't be blank");
      });
    });
  });
});

Our failure specification uses the jasmine-ajax request object’s #response method to simulate a non-success response from the server.

Running Your Specs Headlessly

The Jasmine gem comes with two Rake tasks that you can use to run your specs. rake jasmine will start a Ruby web server to allow you to run your specs from a browser. For those of us who live on the command line and can in no way fathom running tests from a browser there is rake jasmine:ci. Unfortunately this will use Selenium to run your tests in Firefox.

Instead I like to use the jasmine-headless-webkit gem to run specs headlessly with WebKit. jasmine-headless-webkit includes a command line app that can be used to run every spec at once or a single spec at a time.

# run all specs (will try to locate your jasmine.yml config to determine what specs to run)
$ jasmine-headless-webkit

# run an individual spec
$ jasmine-headless-webkit spec/javascripts/models/task_spec.js

jasmine-headless-webkit also supports specs written in CoffeeScript.

Cleaner Specs with CoffeeScript

CoffeeScript is a fantastic language that compiles into JavaScript. Syntactically it really cleans up JavaScript and feels a like a mixture of Ruby and Python.

Here’s a previous spec converted to CoffeeScript:

describe 'NewTaskView', ->
  describe '#render', ->
    beforeEach ->
      setFixtures '<div id="taskForm" />'
      @newTaskView = new NewTaskView model: new Task

      @newTaskView.render()

    it 'displays a form to enter a task', ->
      expect($(@newTaskView.el)).toContain('form')

Basically CoffeeScript is JavaScript with no semi-colons, curly braces, 1/2 the parentheses, a simpler function syntax, Ruby style instance variables, Python style formatting and a whole lot of functional goodness. If you haven’t checked it out yet I suggest you do. Although once you do, you won’t be going back to plain old JavaScript anytime soon.

Start Testing Today

Jasmine is a mature, proven JavaScript testing tool. Coupled with a great community turning out testing tools everyday, there is no longer any excuse to be writing untested JavaScript. So quit putting it off and give it a try today.

About Jared Carroll

After a short stint in the fashion industry Jared found his true calling at Carbon Five. Yes... he looks like a serial killer in this photo. But really he is as gentle as a flower.
This entry was posted in Process, Web. Bookmark the permalink.
  • Michael Wynholds

    It seems like JS testing has been around forever (ie: several years), but I always felt the tools out there sucked. I haven’t used Jasmine yet. Do you have a similar feeling about previous tools? And is Jasmine the silver bullet, or is it just a little better than previous tools? What I’m basically asking is this – will Jasmine make unit testing JS easy enough that testing becomes the norm (like it is in Ruby)?

    Two other things:
    1. I’d love to see an example of some output from the jasmine-headless-webkit test runner.
    2. I’d love to see another post focused on CoffeeScript.

    • Jared Carroll

      Mike,

      I started out with JsUnit, then JSpec, and jQuery’s QUnit. JsUnit and JSpec are no longer maintained and QUnit is a classic xUnit style of testing. After using RSpec in Ruby I prefer the BDD style of specifications rather than tests; so naturally I decided to use Jasmine. I’d say Jasmine is better than previous JS testing tools because its got a good community, is actively maintained and addresses common JS use cases. As for developers writing JS tests, the community just needs to get behind testing like the Ruby community did; with the appearance of new testing tools I think it will become more commonplace.

      Check out the jasmine-headless-webkit page for an example of its output. There’s nothing surprising there, its just like traditional testing tools.

      I’ll see what I can do about CoffeeScript ;)

  • Rob Pak

    I have been using backbone.js and I have separated my Jasmine specs according to the MVC pattern. My directory structure for my specs look like:

    spec/javascripts/
    - controllers
    - models
    - views

    With a spec for each layer, I have found that my client-side TDD flow has improved as I build out my features from the top down.

    Sidenote: one big advantage that I noticed when using jasmine-headless-webkit was the speed. All of my specs finish running under a second using jasmine-headless-webkit.

  • http://gilesbowkett.blogspot.com Giles Bowkett

    I like to use the Node.js fork of Jasmine, jasmine-node, to run specs on the command line. The upside is it’s crazy fast. The downside is that DOM and jQuery integration were, last I checked, kind of a PITA. However, the more you factor your code in an MVCish way, separating logic from view specifics, the less that matters, and it’s really nice to have even just a model-centric section of your code base where JS specs run headless and lightning-fast.

  • http://www.ApproachE.com Dmytrii Nagirniak

    I just want to add couple of things:

    1. You can automatically run specs as files change using guard and guard-headless-webkit.
    2. You can automatically compile Rails 3.1 assets using guard-rails-assets (https://github.com/dnagir/guard-rails-assets)

    With these 2 gems it the BDD gets much closer to you.

    Cheers,
    Dmytrii.

  • http://agiledh.com Filipe Giusti

    Seems like jasmine-ajax is incompatible with jasmine-jquery#loadFixtures function as it uses an ajax call to retrieve the fixtures. Have you already fallen into this problem?

    • http://www.carbonfive.com August Jaenicke

      Filipe,
      The lastest version of jasmine-query (1.3.0) adds a preloadFixtures method to get around the conflict with jasmine-ajax. Using preloadFixtures worked for me – but it seems you need to call it outside of the it/describe block where you call loadFixtures. Then all is good.

      See this post by velesin for more information:
      https://github.com/velesin/jasmine-jquery/issues/15

  • Jared Carroll

    Filipe,

    I’m sorry I haven’t run into that issue. I would say move towards using jasmine-jquery#setFixtures and define all your test data in your tests. That’s currently my strategy.

  • Charles Serra

    Nice article, Jasmine BDD seem’s really awesome.. It is possible to save a set of result directly on a database? It would be very usefull.. Thanks!

    • https://github.com/johnbintz/jasmine-headless-webkit John Bintz

      jasmine-headless-webkit doesn’t save the entire set of results, but I can definitely see the value in doing so for CI servers. RIght now, it can write out a basic report file, but that’s primarily for Guard notifications. Open a new issue on the GitHub project (https://github.com/johnbintz/jasmine-headless-webkit) with the kind of format you’re looking for and we can get it added to a future release. :)

  • Jared Carroll

    Charles,

    I’m not aware of any easy way to save the results from running a full test suite with jasmine-headless-webkit to a database. You would probably have to roll your own script.

    • Charles Serra

      Thanks you for your answer! I browse the jasmine code source for a while, and all results a contained in a variable. Currently, I don’t know if I can get it from an external script.. but I think it should be possible… Let you know if I find something :)

  • vish

    hi,

    The “loadFixture” helper method is great but, this requires me to have that HTML snippet both in my pages and also in the snippet file to be called by “loadFixture”. This is not optimal since, these two things can go outta sync and cause issues. Any help/suggestions to get around this issue?

    Thank You,
    Vish

    • Jared Carroll

      Vish,

      I agree in that keeping the two in sync is a pain. Here’s an interesting solution that uses regular RSpec tests to generate html fixtures for JS tests: http://pivotallabs.com/users/jb/blog/articles/1152 . Another approach would be to use client-side rendering for all your markup (think backbonejs).

      • vish

        great. i will check out that RSpec stuff. Also, even if i use all client-side templates, i would still have to have them in 2 places right? One in my page itself and second in a separate file so that loadFixture can load it in?

        Thank You,
        Vish

        • http://www.carbonfive.com August Jaenicke

          Vish,
          I’m currently using knockout + jquery.tmpl with testing in jasmine. I use a modified version of the rspec helper from that pivotal post to process my haml files (with the embedded templates) into a tmp directory in my project. My only significant mods to the pivotal code was to update it to work with the latest rails/rspec. Then I use jasmine-jquery’s new preloadFixtures to load from that directory. (I have to use preload instead of load due to the conflict between mock-ajax and jasming-jquery). Works great. The only catch is that I have to remember to run the rspec that saves the processed haml before running the jasmine tests when I modify the original haml. Other than that, it’s been a great way to test.

        • Jared Carroll

          @vish,

          By using client-side rendering I was thinking of generating the markup via a framework like backbonejs and then programmatically accessing it in a Jasmine test via a backbonejs View object. This would require more JS but it would minimize and possibly eliminate the need for html fixtures. Although this is most likely overkill for most apps.

          Here’s a good intro to testing backbonejs: http://tinnedfruit.com/2011/04/26/testing-backbone-apps-with-jasmine-sinon-3.html#views . A little old but most of it still applies.

  • http://leandromoreira.com.br leandro

    Hey,

    I’m trying to use the jasmine by the jessie (node.js headless) and then I would like to test:

    /spec/cpu_spec.js
    describe(“CPU”,function(){
    it(“should change the pc”,function(){
    expect(cpuStep()).toEqual(5);
    });
    });

    /lib/cpu.js
    function cpuStep(){return 5;}

    And then run it through terminal: jessie /spec -f nested

    But for sure the cpuStep function is not defined, how can I include / load or anything else to append this dependencies of my spec?

    • Jared Carroll

      @leandro – I haven’t yet tried out jessie but it seems to be based on jasmine-node which like normal jasmine will look for non-test code in the directories specified in your jasmine.yml file.

      • http://leandromoreira.com.br Leandro

        And where stands jasmine.yml on the root? I’ve tried spec/javascripts/support/jasmine.yml and it doesn’t worked.

        • http://leandromoreira.com.br Leandro

          Ohh I just found out, you need to put the jasmine.yml inside the spec structure. Thanks.

  • Pingback: Jasmine resources for Javascript testing - Life's catechism

  • Believe2014

    After the basics, check out http://believeblog.azurewebsites.net/post/learn-knockoutjs-with-its-father

    You’ll see how all the little pieces (Visual Studio, KO, Jasmine, Karma) put together in action.