JavaScript BDD, with Jasmine, without a browser

I’ve been test driving the domain of my build radiator, XFD with the lovely Jasmine BDD framework for JavaScript. Jasmine is lovely. Browsers aren’t. Spawning a new browser to run your tests has issues for me:

  • Spawning a browser takes time and ruins my flow,
  • I’m trying to drive out logic – having a browser present will lead my design to un-natural couplings, and
  • It makes Continuous Integration that much harder

So I started investigating what I could do with Rhino and Envjs to make testing with Jasmine more awesome. Ingvald Skaug had been there before. It took me some time to really understand how the pieces fit together works, so I thought I’d expand on it.

Step 1: Check that Jasmine is working

I would have saved so much time if I’d started with this bit. What you need to do is download the core of Jasmine and stick it in your project. I started with the Jasmine RubyGem that spawns a browser and does the plumbing, but for this it’s back to basics. In my project it’s checked in at lib/jasmine-1.0.1. You need an HTML file to reference all the scripts and kick off the tests. Here’s an example derived from the Jasmine docs:


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
  <title>Jasmine Test Runner</title>
  <link rel="stylesheet" type="text/css" href="lib/jasmine-1.0.1/jasmine.css"></link>
  <script type="text/javascript" src="lib/jasmine-1.0.1/jasmine.js"></script>
  <script type="text/javascript" src="lib/jasmine-1.0.1/jasmine-html.js"></script>
 
  <!-- include source files here... -->
  <script type="text/javascript" src="src/Player.js"></script>
  <script type="text/javascript" src="src/Song.js"></script>
  
  <!-- include spec files here... -->
  <script type="text/javascript" src="spec/SpecHelper.js"></script>
  <script type="text/javascript" src="spec/PlayerSpec.js"></script>

</head>
<body>
  
<script type="text/javascript">
  jasmine.getEnv().addReporter(new jasmine.TrivialReporter());
  jasmine.getEnv().execute();
</script>

</body>
</html>

In my real project I generate this file at build time from an ERB template, to make sure I get all the source files and tests. However you do it, make sure it works in a browser first. Yes. Really.

Step 2: Get the bits that you need

In my lib directory I have:

  • js.jar – which is the Rhino implementation of JavaScript. I already used this to run JsLint as part of my build
  • env.rhino.1.2.js – which is Envjs – a DOM implementation written in JavaScript.
  • jasmine.console_reporter.js, jasmine.junit_reporter.js and envjs.bootstrap.js – all from Larry Myers’ excellent Jasmine Reporters project. Jasmine Reporters is really what glues everything together.

Step 3: Wire up Jasmine Reporters

You can have many Jasmine reporters wired up in the SpecRunner.html. In this example I’m leaving two in – the TrivialReporter that gives HTML/CSS reports, and the ConsoleReporter, which we’ll use later. Here’s the edit to the SpecRunner file now:


<script type="text/javascript">
  jasmine.getEnv().addReporter(new jasmine.ConsoleReporter());
  jasmine.getEnv().addReporter(new jasmine.TrivialReporter());
  jasmine.getEnv().execute();
</script>

Step 4: Put it all together

Here’s where it all happens. In my example I use a shell script but in real life the Rakefile that generates the SpecRunner file also fires up the JVM and checks STDOUT for error messages.

#!/bin/bash
java -jar lib/js.jar -opt -1 lib/envjs.bootstrap.js SpecRunner.html

envjs.bootstrap.js is worth examining, too:

load('lib/env.rhino.1.2.js');

Envjs.scriptTypes['text/javascript'] = true;

var specFile;

for (i = 0; i < arguments.length; i++) {
    specFile = arguments[i];
    
    console.log("Loading: " + specFile);
    
    window.location = specFile
}

This file takes the list of HTML files that you give it and tells the fake browser inside the JVM to load each one. Jasmine then fires and runs your tests:

jsimpson@curie:~/Documents/workspace/jasmine-rhino-envjs$ ./jasmine 
[  Envjs/1.6 (Rhino; U; Linux i386 2.6.32-26-generic; en-US; rv:1.7.0.rc2) Resig/20070309 PilotFish/1.2.13  ]
Loading: SpecRunner.html
Runner Started.
Player : should be able to play a Song ... 
>> Jasmine Running Player should be able to play a Song...
Passed.
when song has been paused : should indicate that the song is currently paused ... 
>> Jasmine Running when song has been paused should indicate that the song is currently paused...
Passed.
when song has been paused : should be possible to resume ... 
>> Jasmine Running when song has been paused should be possible to resume...
Passed.
when song has been paused: 4 of 4 passed.
Player : tells the current song if the user has made it a favorite ... 
>> Jasmine Running Player tells the current song if the user has made it a favorite...
Passed.
#resume : should throw an exception if song is already playing ... 
>> Jasmine Running #resume should throw an exception if song is already playing...
Passed.
#resume: 1 of 1 passed.
Player: 8 of 8 passed.
Runner Finished.

There’s also a JUnit compatible XML reporter, courtesy of Larry. This lets you make the Continuous Integration server report test results as usual.

Summary
I’m very impressed. All of my tests that used to run in the browser run headless, with some fiddling of paths. I’m using the Jasmine JQuery plugin, which probably saved my bacon on the test that is too tightly coupled to views. I’ve collected the example on GitHub.

Props to Ingvald, Larry, and the Jasmine, Rhino and Envjs teams. You guys rock.

Tagged ,

13 thoughts on “JavaScript BDD, with Jasmine, without a browser

  1. […] a little magic duct-taping session, Julian Simpson @ The Build Doctor combines Jasmine and Rhino (Mozilla’s JavaScript for Java implementation) with a few choice libraries. The result is […]

  2. Julian, thanks for the post. I was able to get my setup
    working and I love it! One suggestion for some of the readers who
    are not as savvy with Java development is to add more details about
    the installation and configuration of Rhino (js.jar) and EnvJS.
    Also for people who are planning on using a Windows machine it is
    necessary to apply a patch for EnvJS as the bootstrap code does not
    currently handle Windows UNC path. See

    https://github.com/thatcher/env-js/pull/23

  3. makuchaku says:

    Jasmine console driver – just what I needed to plug the hole in my headless testing!
    Thanks a ton :)

  4. Wes says:

    this is great, thanks. Have you tried using it in a CI automated build tool? can it fail the build on a test failure?

  5. yuxel says:

    We also build a tool named “qasmine” to run jasmine specs from command line. You can also integrate it into an CI environment (we’ve integrated it in hudson) and aslo you can write some git hooks.

    have a look at https://github.com/tart/qasmine

  6. This post inspired me to create my own custom JUnit runner which runs Jasmine specs in java, as a plain JUnit test. This means you can nicely integrate these tests into the build and other java tests!

    Please take a look at https://github.com/jefklak/jasmine-junit-runner – contains a README and includes examples.
    If you have any suggestions, just let me know.

    Of course it uses Rhino + Envjs :)
    Thanks,
    — Wouter.

  7. Erik Mason says:

    Thank you for this! I’ve been struggling with node-jasmine-dom for over a month to try and get it to run in both Windows and on a Mac. I’ve got a little bit of work left to make this run on Windows, but it will work.

    One question, have you had trouble with beforeEach and afterEach functions in jasmine? We have tests that are failing in the build that work when you launch them in a browser and the only thing that I can think of is that the beforeEach and afterEach functions aren’t running. Still looking into it though.

  8. Jan Kester says:

    I finally got it working too. I had to solve two problems: the rhino env.js file did not put file:/// properly in front of the filepath name when you specified a local file only. Instead it only put file://. I read some people on internet, who fixed this:
    if (!base) {
    base = ‘file:///’ + Envjs.getcwd() + ‘/';
    }

    Then, I got failures all the time for TypeError. No clear error message when running with java -jar js.jar. Opening in a browser, firebug showed me, that I had not included the new reporter.js files in my html page ( a beginner’s mistake :-)).

    To get junit output as well, include the JunitXmlReporter to Jasmine env.

    Regards, Jan Kester.

  9. sampath says:

    Hi, Nice work. but I couldn’t run it. please advice me. I got typeerror.

    error loading script [object HTMLScriptElement] TypeError: org.mozilla.javascript.Undefined@acfec48 is not a function, it is undefined.
    TypeError: Cannot call method “replace” of null

  10. EnvJS sounds like a bit too much abstraction to me, in case anyone is looking for alternatives based on PhantomJS check these out:

    Grunt / Node based:

    https://github.com/jasmine-contrib/grunt-jasmine-runner

    Ruby / Guard based (by yours truly):

    https://github.com/daaain/Guard-Jasmine2JUnit

  11. Henrik Tellander says:

    @sampath:
    see Jan Kester’s comment above yours, you probably forgot to include the appropriate reporter file:

    <script type="text/javascript" src="lib/jasmine.console_reporter.js"></script> <script type="text/javascript" src="lib/jasmine.junit_reporter.js"></script>

Comments are closed.

Follow

Get every new post delivered to your Inbox.

Join 3,287 other followers

%d bloggers like this: