Isolated UI testing with PhantomJS

If you’re working on a JavaScript web application that uses APIs to separate the concerns of UI and domain model then you can test your UI as an isolated concern.  Mock the Ajax responses from your API, write functional UI tests, and make assertions on outgoing requests.

I’ve posted before no how to use JSTD for unit testing JavaScript.  Unit level test coverage encourages better design and supports refactoring through fast  feedback.  It really does make a difference to code confidence and although I personally do minimal TDD, writing tests as you go can help massively in understanding problems and fleshing out solutions.

Awesome, great for logical code but… I have found that unit tests for behavioural UI code provide little in the way of qualitative feedback and TDD can actually hinder UI problem solving by abstracting the developer away from the real problem they’re trying to solve, the UX problem.

So given the lack of total coverage with unit tests its understandable that we’d want to build functional UI tests as well.  The most popular tool for that would be Selenium, a tool for automating web browsers.  Selenium is useful for integration testing, that is, end-to-end, make sure the website/webapp works completely in its entirety. But Selenium is slow, super slow; especially with web applications where you need to set up real data for each test.  Fine for integrated testing, not fine for functional UI testing. Integration and UI testing are different things and should be done separately.

There is of course a new kid in town. PhantomJS attracted me for two reasons.  The first being that it’s a headless browser, so runs from the command line and is really quick. The second is that it lets us easily interact with web pages using JavaScript (crazy idea, who came up with that?).

PhantomJS by itself is pretty raw, I decided to use it with the CasperJS library to help build up navigation scenarios. CasperJS gives us all the cool test assertion stuff as well as handy methods for finding elements and raising DOM events.  Now here’s the clever bit. Sinon.JS provides an XHR mocking interface, it can block all XHR requests and let you respond to these requests with fake data.  By injecting Sinon.JS on to the page at the beginning of a test suite not only can we isolate the UI from the back-end API, but we can drive the UI with fake data and even make assertions on outgoing requests.

Below I’ve drafted a small example of what I mean (It doesn’t work, but represents a simpler implementation of what I have already built). CasperJSs page.initialized event is very useful for setting up XHR mocks early on, before any window.onload API calls are made.

casper.
start(rootUrl).
addListener('page.initialized', function(){
  casper.page.injectJs( libs+'/sinon.js' );
  casper.evaluate(funciton(){
    window._xhr = sinon.useFakeXMLHttpRequest();
    window._requests = {};

    window._xhr.onCreate = function (xhr) {
      var headers, body;

      if(request.method === 'GET'){

        headers = { "Content-Type": "application/json" };
        body = '[{ "id": 12, "comment": "Faking it is fun!" }]';

        // fake some data to make the UI do something
        xhr.respond(200, headers, body); 
      } else {

        window._requests[xhr.url] = true;
      }
    };
  });
}).
thenOpen(pageUrl, function(){
  casper.click('#my-button'); // trigger some ajax
}).
then(function(){
  var request = casper.evaluate(function(){
    return window._requests;
  });

  // test the HTTP interface
  casper.test.assert( request[posturi] === true, 'Should have posted a request'); 

  // test the User interface
  casper.test.assertTextExists('Faking it is fun!', 'Should have the fake comment in the UI');

});
// ...

Setting up Fake Ajax responses this way is a little clumsy, the solution I built at work abstracted this code away so that in the tests we only call a fakeResponse method and a assertRequest method. PhantomJS in general is very hackable, really easy to extend.

I’ve evangelised this approach to testing in my workspace and we have now fully adopted automated, isolated UI testing using these technologies. An unexpected side effect of faster feedback from automated UI testing is that we write fewer unit tests. If the feedback time is quick, may as well push as much of the testing to the front as possible. We still write unit tests for complicated logic driven JS components but get the bulk of coverage from the UI tests. There’s a balance obviously but this inevitably means less test duplication but better coverage. Oh and of course we still have Web integration tests (using Selenium) but write far fewer, tests aimed purely at end-to-end connectivity not functional UI.

So we now have fast, smart, flexible test coverage over our JavaScript application. Writing UI tests is no longer a burden. To quote one of my colleagues: “UI testing is fun again!”.

About these ads

3 thoughts on “Isolated UI testing with PhantomJS

  1. Thanks for this example, James. Through it, I was able to work out something suitable for my work place. I’m hoping the other engineers will catch on! :)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s