Sunday, May 12, 2013

Javascript unit tests with QUnit, PhantomJS, driven by Ant

The project I’m currently working on (Qoll.it) is fairly Javascript heavy so it was essential to have at least some core functionalities unit tested before they can be submitted to the repository.

Running Javascript unit tests in the browser and relying on developers really doing it before submitting code may not be the best idea – they might be in a hurry, or even worse, can get a bit lazy. Let’s see how to make the Javascript unit testing part of the build process.

Javascript unit testing

There are a lot of different frameworks for Javascript unit testing, so as long as we are not changing the syntax radically of the testing between Java/C#/Javascript, Javascript unit testing will be as normal as testing compiled code.

However, some of the frameworks use a very different way of testing:

describe("Package X", function() {
  it("really should be true", function() {
    expect(true).toBe(true);
  });
});

Even though the syntax is entertaining to read, it is substantially different from what we would see in a Java codebase. I think just because we can, we shouldn’t change things, and a recent a study at Cornell University highlighted that even a tiny change in a familiar source code snippet can slow down the reading speed or even increase the error rate.

On the other hand QUnit gives us a very clean and familiar structure:

test( "really should be true", function() {
  ok( 1 == "1" );
});

This does the job well, easy to read so I opted for the simplicity of QUnit.

Mocking

There are mock frameworks for Javascript too, so I’ve checked one of them, JsMockito:

var mockFunc = mockFunction();
when(mockFunc)(anything()).then(function(arg) {
  return "foo " + arg;
});

There isn’t anything wrong with the syntax but unlike compiled languages, this framework does not give me as much benefit. I can simply rewrite the above to this:

var mockFunc = function(o){ return “foo “ + o; }

It is a bad trend to add as much libraries to a project as it can handle but ultimately it slows down the production speed. Developers need to learn and debug different technologies instead of building their own codebase, so I finally decided not to add another dependency to my project. Whenever we needed we manually mocked the classes/functions and it was more than enough for our testing needs.

Running the tests

Setting up QUnit is very easy so we can start testing the Javascript codebase within minutes. The biggest drawback is probably the tight integration with a desktop browser so I had to find something more console friendly. PhantomJS is a great WebKit based headless browser, meaning it is able to process any webpages and Javascript code but it does not require any graphical interface to be present. In fact, it does not display anything at all; it is strictly used in an automated environment.

Luckily PhantomJS comes with a script that is able to run QUnit tests right away, we do not even need to script PhantomJS to collect the test results from a HTML page that ran the QUnit tests.

To run a test HTML from the command line, all we need to do it:

./phantomjs run-qunit.js test.html

The test results will be displayed on the console but the tests will be interrupted if they tend to run too long. If someone is planning on running some AJAX integration tests with public services (like Google Maps API) it will be required to increase the timeout in run-qunit.js as the default is fairly short (2-3 seconds in total).

Making part of the build – Ant

Ant is one of my favorite tools as it’s super simple, exists on practically all operating systems and very capable of automating the typical build steps. Even though Ant is able to run a command line tool with custom options I’ve decided to create a shell script that will invoke the Javascript unit tests so I can run that separately and in case I want to modify the way Javascript testing is done I can do it without changing the build file.

The step in the build.xml looks like this:

<target name="jstest" depends="init">
  <exec dir="tests/jstest" executable="bash" failonerror="true">
    <arg line="testjs.sh" />
  </exec>
</target>

and the content of the testjs.sh is:

./runner/phantomjs ./runner/run-qunit.js ./runner/test.html

It is important to flag Ant to stop the build in case on an error as the default options is to ignore errors by setting the “failonerror” attribute to true.

Running on multiple browsers

One of the drawbacks of using PhantomJS is that if someone is using not too well supported Javascript functions or not cross platform Javascript syntax –like an extra comma after the last element in an array-, PhantomJS tests will still pass but other older browsers may fail in live environment. If this is a serious issue, the JS Test Driver project could be useful – it is able to automate many remote browsers and run the unit tests in them, making sure that in actual browsers the tests would pass.

No comments:

Post a Comment