in Code

JavaScript UI Testing with Zombie.js

July 26, 2013

Unit tests can be great to verify your source code, but there are times when integration tests provide a more useful report on the stability of your application. Yes, integration tests (most likely) require more setup than unit tests, and yes, they will (again, most likely) test things outside of the code you wrote. But they also come with a huge upside in that they test what your customers will use — the end product stack. And including integration tests in your continuous integration builds can track how that experience functions over time. Successful integration tests provide you with a real sense that your application performs correctly from end to end.

Although there are many possible integrations to test within an application, I'd like to focus on testing at the UI layer. Within the Node.js environment, there are (of course) many options available to you. One of the more popular tools to assist in UI testing is PhantomJS. PhantomJS is not, however, a test tool but instead a headless WebKit, and requires a testing framework which may need additional configuration. In some cases this might be necessary, but in other cases, a simpler solution is all that's required. Zombie.js provides that with very minimal setup and an intuitive interface for your UI testing needs.

Zombie.js Overview

For those with a Java background, I'd compare Zombie with HtmlUnit. It's a headless browser that provides you with a programatic interface into loading pages, filling out forms, and clicking buttons or links. The folks behind Zombie have provided some pretty entertaining documentation which does a good job of describing how to use Zombie. The very first example on the documentation's home page shows just how easy it is to use, by loading a page, filling out a form, and clicking a button, then verifying the page displayed is correct.

Project Setup for Zombie

Once you've added zombie as a dependency, you're ready to use it in a test. But before we do that, it's important to configure your project to run the server when executing your tests. In a typical Node/Express project, you specify an app.js (or index.js) file which details the configuration of your server while also starting your server on a configured port. We'll want to separate the configuration from the server start so that we can choose when to start the server from within the tests.

In my simple-todo project, I've taken the "app.listen" code from my app.js file, and moved it to an index.js file. My app.js file now ends with:

// instead of starting the application here, export the app so that it can
// be loaded differently based on the use case (running the app vs testing)
module.exports = app;

This allows me to include the app.js in my newly created index.js, which contains the following:

var app = require("./app");

app.listen(app.get("port"), function() {
  console.log("Express server listening on port " + app.get("port"));

Now instead of launching my server with "node app.js" I simply run "node index.js" with the end result being the same. Additionally my tests can include the configuration of app.js without starting the server.

Creating a Zombie Test

I prefer to use Mocha/Chai as my test framework, which has great integration with Zombie. I've created a Zombie based test in my simple-todo project which I'll be discussing below. The full source of the test can be found in the GitHub repository of the simple-todo project.

To begin, bring in the dependencies for Zombie and assert, as well as the app.js file we created above. This allows us to start and stop our Express server within the test, which is necessary when running the integration tests.

var assert = require("chai").assert,
    Browser = require("zombie"),
    app = require("../../../app");

Mocha provides hooks to execute code before and after all the tests run, and before and after each test runs. Using these hooks, we can start and stop our Express server with the following code:

before(function() {
  // before ALL the tests, start our Express server (on a test port, 3001)
  server = app.listen(3001);

after(function() {
  // after ALL the tests, close the server

The last bit of test setup is creating a new Zombie browser. It's useful to create a new one for each test so that we know we're getting a clean slate at the start, and no test will adversely affect the other.

beforeEach(function() {
  // before EACH test, create a new zombie browser
  browser = new Browser();

From here, you can begin to write your tests. Using a combination of the documentation from the Zombie API and the Zombie CSS Selectors, it's pretty easy to begin with some simple tests. Keep in mind that because of the asynchronous nature of JavaScript, you'll probably need to include a done callback in your tests to avoid the test from finishing before the browser has loaded, or an action is complete. An example may be worthwhile here — the first test I wrote was to load the simple-todo main page and verify it's page contents:

it("should show the login page to begin", function(done) {
  browser.visit(todoUrl, function() {
    assert(browser.text("#page-heading") === "Simple Todo",
      "page heading must match");
    assert(browser.text("#login-heading") === "Welcome to Simple Todo",
      "login heading must exist and match");

    // done with test

This test begins by using Zombie's browser instance to "visit" the URL of the simple-todo application. A callback is provided which is executed once the page finishes loading, and within that function we provide some assert statements to verify the page data. Here again we use Zombie's browser to grab text from the page, using CSS selectors to find the page heading and login heading. And after all that is complete, we call the done callback to signal to Mocha that our test is complete.

More Fun with Zombies

Zombie provides many more abilities than what I've described in the test above, and I encourage you to check out the full API documentation. They provide a basic callback structure which I've used in the test above, but also provide a promise model which I've used in the Zombie test within the simple-todo project. I've found Zombie to be a very quick and easy way to create integration tests for your web application. And because it integrates seamlessly with Mocha, executing your integration tests along side of your existing unit tests is no work at all.

And with all that talk about zombies, I'm sure you're all ready to listen to this.