HTML fixtures in Jasmine (using jasmine-jquery)

!!! THIS BLOG IS NO LONGER ACTIVE – CHECK OUT MY CURRENT BLOG AT VELESIN.IO !!!

My comparison of JavaScript BDD frameworks resulted in choosing Jasmine to be my JS testing framework of choice. However, I’m doing a lot of jQuery based development, while Jasmine by design tries to stay DOM agnostic. It is not that big problem, as Jasmine is really flexible and open (I’ll show in a moment how to manipulate DOM with jQuery using plain-vanilla Jasmine), but with a couple of tweaks packed into my jasmine-jquery plugin, jQuery users can make their life even easier.

First, let’s start with the simplest approach possible to test jQuery code in your Jasmine specs. The most straightforward way is just to create a “fixture” jQuery element inside your spec, invoke your plugin on it and then set your expectations against the fixture element (to verify if the plugin modified fixture element correctly):

describe('simplest jQuery plugin test', function () {
  it('should use inline fixture', function () {
    var fixture = $('<div>some HTML code here</div>');
    fixture.myTestedJqueryPlugin();
    expect(fixture).toSomething();
  });
});

Of course, for any non trivial plugin you’ll need more than one spec to fully test its behavior, so the above example in reality will look more like this:

describe('more realistic jQuery plugin test', function () {
  var fixture;

  beforeEach(function () {
    fixture = $('<div>some HTML code here</div>');
  });

  it('should use inline fixture', function () {
    fixture.myTestedJqueryPlugin();
    expect(fixture).toSomething();
  });

  it('should use inline fixture again', function () {
    fixture.myTestedJqueryPlugin();
    expect(fixture).toSomethingElse();
  });
});

Above solution is still far from perfect though. One of the problems is that jQuery fixture element is created only in memory and never added to DOM. In effect, many tests (e.g. related to element visibility or dimensions) won’t work correctly. In order to solve this, we need to add our fixture to the DOM and, to avoid test interdependencies, we must clear the DOM between tests.

describe('DOM based jQuery plugin test', function () {
  beforeEach(function () {
    $('#fixture').remove();
    $('body').append('<div id="fixture">...</div>');
  });

  it('should use DOM fixture', function () {
    $('#fixture').myTestedJqueryPlugin();
    expect($('#fixture')).toSomething();
  });

  it('should use DOM fixture again', function () {
    $('#fixture').myTestedJqueryPlugin();
    expect($('#fixture')).toSomethingElse();
  });
});

Now it should work for all kinds of jQuery related tests, and the beforeEach doesn’t look that bad (only 2 lines of code), but unfortunately such a simple one-liner fixtures will rarely suffice for non trivial, real-life plugins. And believe me, inlining complex, multiline HTML strings in your test code is ugly, turning your well factored test code into one big tangled mess. Also, to keep your tests clean, you may want to reuse fixtures not only between tests in one file, but also between different test files. The answer here is loading fixtures from files. We can change our code to something like this:

describe('test with fixture files', function () {
  beforeEach(function () {
    $('#fixture').remove();
    $.ajax({
      async: false, // must be synchronous to guarantee that no tests are run before fixture is loaded
      dataType: 'html',
      url: 'my_fixture.html',
      success: function(data) {
        $('body').append($(data));
      }
    });
  });

  it('should use DOM fixture', function () {
    $('#fixture').myTestedJqueryPlugin();
    expect($('#fixture')).toSomething();
  });

  it('should use DOM fixture again', function () {
    $('#fixture').myTestedJqueryPlugin();
    expect($('#fixture')).toSomethingElse();
  });
});

Now your fixtures can be neatly organized into separate files and don’t pollute your test code. An added benefit is that they don’t have to be static – my_fixture.html file can be now e.g. generated via normal Rails action etc. However, beforeEach have grown a little. And this is just a tip of an iceberg. Right now the fixture file is reloaded via AJAX before each of your tests, which results in a major penalty to your test suite’s speed – some kind of fixture caching would be welcome. Also, what if you want to load more than one fixture? You’ll then have to duplicate fixture loading and DOM clean-up code. And what if for some reason you want to load fixture from external file, but don’t want to append it automatically to DOM, but rather assign to a variable inside your test? Again, this can be easily done, but it will add another variation to your beforeEach code. Even worse, you’ll probably use fixtures in more than one test file, so your fixture loading code will end-up copy pasted all over the place. As you can see this can quickly go out of control for any non trivial project.

jasmine-jquery to the rescue! ;)

It extends Jasmine framework with a robust set of methods to handle HTML fixtures. It supports also fixture caching to speed-up your test suite and auto clean-up of DOM between tests. With jasmine-jquery you can write your tests like this:

describe('test with jasmine-jquery', function () {
  it('should load many fixtures into DOM', function () {
    loadFixtures('my_fixture_1.html', 'my_fixture_2.html');
    expect($('#jasmine-fixtures')).toSomething();
  });

  it('should only return fixture', function () {
    var fixture = readFixtures('my_fixture_3.html');
    expect(fixture).toSomethingElse();
  });
});

And that’s all. Yes, there doesn’t have to be beforeEach at all. Fixtures are automatically appended to DOM by loadFixtures method and automatically cleaned-up between tests. Apart from loadFixtures and readFixtures jasmine-jquery provides other useful methods, so don’t forget to check jasmine-jquery GitHub page for the full documentation.

Apart of HTML fixtures support, jasmine-jquery provides also a robust set of Jasmine custom matchers for jQuery – I’ll present them in more detail in next post.

Advertisements

34 Comments

  1. fschwiet said

    Thanks for blogging about Jasmine, I discovered it via your blog and am liking it.

    About using $.ajax({async:false}). I was also using this to load some external html that had template definitions. I found that it would not reliably finish synchronously if I made the call before the documents ready handler (on Firefox). For this to work reliably in your case I think you’d need to be sure to not run the tests until the documents on ready handler as well.

  2. velesin said

    Thanks for your input!

    I wasn’t aware of the async: false issue. I use jasmine-jquery in my tests to load fixtures from external files and didn’t hit any problems with it, but will take a closer look at it.

    I wonder if placing Jasmine initialization code on a runner page inside jQuery’s $().ready() could help solve such issue (and if they would play well together)?

  3. thedeeno said

    Thanks for posting about this, it really fast tracked my understanding of dom testing with jquery + jasmine.

    Quick question: do you know how to run jasmine suites in multiple browsers and automatically report results to the console? It looks like jspec can do this but I have yet to find a comparable way to do this with jasmine.

  4. velesin said

    @thedeeno

    Yes, JSpec can do this natively. For Jasmine I know of two ways that allow achieving similar effect:
    – via jasmine-gem (http://github.com/pivotal/jasmine-gem) – if you are using Ruby (not necessarily Rails, it works also e.g. for Merb and in “plain” Ruby)
    – via integration with jsTestDriver framework (http://code.google.com/p/js-test-driver/)

    Regarding first option (jasmine-gem) – it provides a rake task (jasmine:ci) that runs Jasmine tests in a browser via Selenium and then reports results in the console. As far as I remember, this task uses only a single browser at once (I tried it and it works exactly as you want), but the browser is configurable via ENV var called JASMINE_BROWSER, so it should be possible to create your own Rake task that invokes jasmine:ci several times with different settings:
    jasmine:ci JASMINE_BROWSER=ff
    jasmine:ci JASMINE_BROWSER=chrome

    (didn’t try it myself though)

    Regarding second option (integration with jsTestDriver) – I don’t know if you’re familiar with this framework, in short: it is quite a robust platform designed especially to run JS tests on many different browsers at once. It comes with its own built-in assertion framework, but it also provides connectors for other frameworks (among other for Jasmine).

    A few useful links:
    – Jasmine – jsTestDriver adapter: http://github.com/ibolmo/jasmine-jstd-adapter
    – all jsTestDriver adapters list: http://code.google.com/p/js-test-driver/wiki/XUnitCompatibility
    – my previous blog post comparing xUnit-style JS testing frameworks (jsTestDriver is also described here): https://testdrivenwebsites.com/2010/04/19/java-script-xunit-style-frameworks-comparison/

    I tried jsTestDriver with its built-in assertion framework and it works really nice. I didn’t try it with Jasmine connector yet, but this project seems actively maintained, so I hope it will work as advertised.

  5. fschwiet said

    “I wonder if placing Jasmine initialization code on a runner page inside jQuery’s $().ready()”

    Specifically what I did was move two lines from Jasmine’s SpecRunner.html into an onready handler. They run the tests, so it should be sufficient:

    jasmine.getEnv().addReporter(new jasmine.TrivialReporter());
    jasmine.getEnv().execute();

    The problem didn’t repro all the time, but if you reload enough you might see it. If the files are hosted remotely I imagine it would repro more consistently

  6. thedeeno said

    @Velson

    Awesome. I was worried that the jasmine:ci task was the thing I was looking for. It wasn’t working for rspec2 projects. I’ve integrated some changes from other jasmine forks and now mine (thedeeno/jasmine-gem.git) is working. I’ll be making that wrapper task soon, unless JsTestDriver rocks to hard.

  7. thedeeno said

    I’ve posted an issue in the github repo. A bunch of the matchers are _always_ passing when used a certain way. I might be using them wrong, but it seemed like the intuitive way to use them.

  8. velesin said

    @fschwiet: Thanks for pointing this out! A very useful hint.

    @thedeeno 1: really nice to hear :) I’m starting a new Rails 3 project in a couple of days, your Rails3/RSpec2 branch will come in handy. I’ll definitely take a look at it.

    @thedeeno 2: I cannot reproduce this issue. I’d be grateful if you can you look at my coment on GitHub and help me with providing some more details.

  9. Siwei said

    awesome plugin!

    I am using prototype 1.6 in current project, however there’s no easy way to load fixtures, it seems.

    Your plugin gives me a good idea and suggestion to implement a “prototype” version.

    I will let you know once I have done this(if having enough time :p )

    thanks again~

  10. velesin said

    I think this is a good idea :) I’m not into a prototype, so if you’re willing to port my plugin, feel free to do it (and to reuse any parts of my code base you need).

  11. […] jasmine-jquery – most useful for interaction with the DOM. Provides management of remote and local markup dependencies and a comprehensive set of Jasmine matchers to verify end-result elements, attributes and even event handlers. Read more here. […]

  12. I’m using jasmine from maven, and while the .ajax approach allowed me to load the html document, the jasmine-query way didn’t seem to work. Any idea?

  13. Nevermind, I was assuming that the fixtures got loaded relatively to the testRunner’s location. I found out I had to use jasmine.getFixtures().fixturesPath…

  14. Heyas, Is there any way of loading 2 fixtures and set 1 as the opener of the second?

    Example:

    window_1 opens window_2 by clicking a link (not important to test) but then some interactions with JS happends in window_2 and the JS code finishes by making some changes on window_1 by calling sometime at the end opener.$(‘#my-selector-on-window_1’).append(‘bla bla bla’);

    My problem is how to tell jasmine the relationship as window-opener between window_1 and window_2

    Ideas?

  15. velesin said

    By default this is not supported, I think Jasmine was designed to be run inside a single window and didn’t even considered such edge case. However, I think it should be possible to do it manually with a little bit of “hacking”.

    I’m thinking about following scenario:

    Your JS code runs in window A. It opens window B in order to manipulate its content in some way. In order to be able to manipulate content of window B, code in window A must keep a handle to window B somewhere.

    So I wonder if it couldn’t be tested like this:
    1. you have a window A with your System Under Test and your Jasmine suite.
    2. in one of your specs your SUT tries to open window B
    3. your Jasmine code intercept this somehow (e.g. by mocking a method opening window B etc.), obtains a handle to window B, and manipulates the content of window B by injecting a HTML snippet into its DOM (loaded from fixture). In such case loadFixtures method will not work, because it expects to be run in the current window, but I think it may be possible to get fixture’s content by invoking getFixtures and then inject this content into DOM of the window B (this is the “manual hack” part I wrote of earlier)
    4. Jasmine lets the rest of your SUT’s code to run (and to do something with window B DOM)
    5. Jasmine again uses window B handle (stored earlier) to access window B DOM, and invokes some matchers on window B content.

    You need also to remember, that opening window B will be async, so you need to use Jasmine’s async facilities in order to maintain proper open-inject-modify-verify order of your test execution.

    PS: this is only a raw idea, it is by no means verified, but I think that with some experimentation there is a chance similar solution may work.

  16. velesin said

    Another though:

    Think also if it really must be verified in such a complex fashion. When you think deeper about it, you can identify two main parts of your code:
    1. opening new window
    2. manipulating DOM in a window

    Maybe you could split the code in an OO fashion into two parts (window opener and DOM manipulator) that could be tested separately, in isolation?

    First test would verify if your code can properly open new window and obtain a handle to some DOM element in this window (but the content of the element would be irrelevant at this point, so you wouldn’t need fixtures).

    Second test would verify if DOM is manipulated properly, assuming that new window is already opened and a handle to DOM element you want to manipulate is already obtained. With these assumptions, you could test DOM manipulation only (what can be done in the same window your tests run in – and thus can use jasmine-jquery fixtures to the full extent).

    Such approach (if it is possible for you) would not only make tests much simpler and easier to run, but probably also improve the design of your SUT by facilitating decoupling.

  17. […] HTML fixtures in Jasmine (using jasmine-jquery) […]

  18. […]  http://railscasts.com/episodes/261-testing-javascript-with-jasmine jasmine-jquery作者的博客:  https://testdrivenwebsites.com/2010/07/29/html-fixtures-in-jasmine-using-jasmine-jquery/ SinonJS: Planning, Cheating and Faking Your Way Through JavaScript […]

  19. Anonymous said

    I’m also having issues using jasmine-jquery in a java/maven environment. I have jasmine-jquery.js in /spec/javascript/helpers and fixtures in /spec/javascript/fixtures

    I do see the plugin wants /spec/javascripts/fixtures (with an s at the end of javascripts.. I’ve hacked this to javascript until I can do something nicer)

    my pom looks like this…

    com.github.searls
    jasmine-maven-plugin
    1.1.0

    test

    jquery-1.6.1.min.js
    log4javascript-1.4.1.js
    jquery/ui/jquery.ui.core.js
    jquery/ui/jquery.ui.widget.js
    jquery/ui/*.js
    **/*.js


    **/*.js
    helpers/jasmine-jquery.js

    ${project.basedir}/src/main/webapp/js

    ${project.basedir}/src/spec/javascript/

  20. Anonymous said

    in any case it would be good to get more explicit instructions on using it jasmine-jquery with the maven plugin.

  21. Anonymous said

    pom again … lets try it in a pre

     
                    
    
                        
                          jquery-1.6.1.min.js
                          log4javascript-1.4.1.js
                          jquery/ui/jquery.ui.core.js
                          jquery/ui/jquery.ui.widget.js
                          jquery/ui/*.js
                          **/*.js
                        
    
                        
    
                            **/*.js
                            helpers/jasmine-jquery.js
                        
    
                        
                           ${project.basedir}/src/main/webapp/js
                        
    
                        
                           ${project.basedir}/src/spec/javascript/
                        
    
                    
           
    
  22. Anonymous said

    sorry… last try.
    <configuration>
    <sourceIncludes>
    <include>jquery-1.6.1.min.js</include>
    <include>log4javascript-1.4.1.js</include>
    <include>jquery/ui/jquery.ui.core.js</include>
    <include>jquery/ui/jquery.ui.widget.js</include>
    <include>jquery/ui/*.js</include>
    <include>**/*.js</include>
    </sourceIncludes>

    <specIncludes>
    <!– this seems not quite right… where does the file go? which is the root? –>
    <include>**/*.js</include>
    <include>helpers/jasmine-jquery.js</include>
    </specIncludes>

    <jsSrcDir>
    ${project.basedir}/src/main/webapp/js
    </jsSrcDir>

    <jsTestSrcDir>
    ${project.basedir}/src/spec/javascript/
    </jsTestSrcDir>

    </configuration>

  23. Hi, thank for your jquery-jasmin work, it’s really useful. I’m testing things with require js and everything works perfectly.

  24. very useful. works a treat.

    Is there a way to override the default directory that it searches for fixtures in? I has to put a lot of”../../../” before my file name to have it backtrack out of the default path

  25. velesin said

    Default fixtures dir is configurable. Put a line like this:

    jasmine.getFixtures().fixturesPath = ‘my/new/path’;

    somewhere in your test runner file or a global before hook.
    (this is described here: https://github.com/velesin/jasmine-jquery#html-fixtures)

  26. Thanks a lot for the jasmine example. Was useful!!

  27. Mansi said

    Can you make it work?
    it(‘chartValue should be an integer’, function () {
    ctrlScope.chartValue = “2”;
    beforeEach(function () {
    this.addMatchers({
    toBeInteger: function () {
    return (typeof(this.actual)) === “number”;

    }
    });
    });

    it(‘Chart Value should be an integer’, function () {
    expect(ctrlScope.chartValue).toBeInteger();
    });
    });

  28. Anonymous said

    Hi!

    At a first glance it seems that there is a problem with the outermost “it”. (first line of Your code). It should be rather “describe”.

  29. Cindy said

    At this time it appears like Expression Engine is the top blogging
    platform available right now. (from what I’ve read) Is that what you are using on your blog?

  30. Hi, I wish for to subscribe for this blog to get most
    recent updates, so where can i do it please help out.

  31. Cecilia said

    Hi there very nice web site!! Man .. Excellent .. Amazing ..
    I’ll bookmark your website and take the feeds also?
    I’m glad to find so many useful information right here in the
    put up, we want develop extra techniques in this regard, thanks for sharing.
    . . . . .

  32. Jacksonville SEO company

    HTML fixtures in Jasmine (using jasmine-jquery) | Test Driven Websites (TDD and BDD in RoR and JS)

  33. Jacquie said

    Good info. Lucky me I discovered your website by chance (stumbleupon).
    I have saved as a favorite for later!

  34. periodico said

    Hi everyone, it’s my first visit at this website, and
    paragraph is really fruitful for me, keep up posting such posts.

RSS feed for comments on this post

Comments are closed.

%d bloggers like this: