As the state of hybrid web development matures, developers are looking to bring their tools and techniques from more "traditional" development to this new medium. One of these techniques, unit testing, can be a bit tricky to setup in PhoneGap/Cordova applications. In this article, we'll look at how to build a test suite for a hybrid app with QUnit: a popular and easy to use unit testing library. We'll look at how to build a test suite, how to run it, and then how to automate it.

Let's get started by looking at what we'll test.

An Application to Test

In order to build a test suite you need something to test. To concentrate on the testing workflow, we'll use a simple example app that uses GitHub's JSON API to load all repositories associated with the jQuery organization.

So that we have some logic to test, the app will color all repos with at least 1,000 GitHub stars orange, and all repos with at least 5,000 stars red.

Here's what the app looks like.

Display of the list of jQuery repositories. Some repos are colored orange and a few are red.

Note: The full source code of the application is at https://github.com/tjvantoll/QUnit-Tester in case you'd like to follow along or refer back to it later. For quick testing you can clone this project's repository directly in Icenium.

Now let's look at the code used to build this. The HTML includes a heading and a <ul> that the repositories will be placed in.

<!-- index.html -->
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="css/index.css">
        <script src="cordova.js"></script>
        <script src="js/jquery.js"></script>
        <script src="js/app.js"></script>
        <title>jQuery Repositories</title>
    </head>
    <body>
        <h3>jQuery git Repositories</h3>
        <ul id="repos"></ul>

        <script>
            document.addEventListener( "deviceready", app.loadRepos );
        </script>
    </body>
</html>

Notice that at the end of the HTML we attach a Cordova deviceready event handler that calls app.loadRepo(). That function is defined in our app's single JavaScript file, shown below.

/* app.js */
window.app = {
    loadRepos: function() {
        return $.getJSON( "https://api.github.com/orgs/jquery/repos" ) 
            .then( function( data ) {
                var className,
                    html = "";

                data.forEach(function( repo ) {
                    className = "";
                    if ( repo.stargazers_count >= 1000 ) { className = "popular"; }
                    if ( repo.stargazers_count >= 5000 ) { className = "very-popular"; }

                    html += "<li class='" + className + "'>" + repo.name + "</li>";
                });

                $( "#repos" ).html( html );
            });
    }
};

Here we call $.getJSON() to invoke GitHub's JSON API and retrieve the list of jQuery repos. When that completes, we create a new list item for each repository and add it to the list. In the process we add a CSS class name to the repositories that have a certain amount of stars. These class names are assigned colors in our app's CSS file.

/* index.css */
.popular { color: orange; }
.very-popular { color: red; }

For now this app is trivial, but suppose we received a series of additional requirements - like displaying more detailed information, or adding sorting and filtering. It would be nice to have tests to cover our existing functionality before we start implementing the hard stuff.

Let's see how we can do that with QUnit.

Building a Test Suite

QUnit is a lightweight and easy to use JavaScript unit testing framework. All you do to run QUnit tests is define an HTML page that includes QUnit's CSS/JS files, and two <div>s: <div id="qunit"> and <div id="qunit-fixture">.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>QUnit Example</title>

        <link rel="stylesheet" href="qunit.css">
        <script src="qunit.js"></script>
    </head>
    <body>
        <div id="qunit"></div>
        <div id="qunit-fixture"></div>
    </body>
</html>

Then you can add tests in JavaScript using the test() method. The following runs two assertions using the most common methods: ok() and equal().

test( "Description of tests", function() {
    ok( true, "ok is for boolean tests" );
    equal( 1, "1", "equal is for comparing two values" );
});

Of course there's a lot more that you can do, but that's all there is to the basics.

So where do we put this? For PhoneGap/Cordova apps, I like creating a tests directory within the project's root folder and placing all unit test related files within it. An example directory structure is shown below.

app
    cordova.js
    css/
        index.css
    js/
        app.js
        jquery.js
    tests/
        index.html
        qunit.css
        qunit.js
        tests.js
    index.html

Here tests.js is setup as the location for all tests for the entire app. Obviously as this application grows it will make sense to break this into more modular files, but a single file makes sense for this simple example.

With this in place, the tests/index.html file now just needs to import the necessary files.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>QUnit Example</title>

        <link rel="stylesheet" href="qunit.css">
        <link rel="stylesheet" href="../css/index.css">

        <script src="../js/jquery.js"></script>
        <script src="qunit.js"></script>

        <script src="../js/app.js"></script>
        <script src="tests.js"></script>
    </head>
    <body>
        <div id="qunit"></div>
        <div id="qunit-fixture"></div>
    </body>
</html>

Here are the tests that we'll place in tests.js to verify the app's current functionality.

asyncTest( "Repository listing", function() {
    expect( 6 );

    var repos = $( "<ul id='repos'></ul>" ).appendTo( "#qunit-fixture" );

    $.mockjax({
        url: "https://api.github.com/orgs/jquery/repos",
        responseText: [
            { name: "Test 1", stargazers_count: 1 },
            { name: "Test 2", stargazers_count: 999 },
            { name: "Test 3", stargazers_count: 1000 },
            { name: "Test 4", stargazers_count: 4999 },
            { name: "Test 5", stargazers_count: 5000 }
        ]
    });

    app.loadRepos().then(function() {
        equal( repos.find( "li" ).length, 5 );
        equal( repos.find( "li" ).eq( 0 ).css( "color" ), "rgb(0, 0, 0)" );
        equal( repos.find( "li" ).eq( 1 ).css( "color" ), "rgb(0, 0, 0)" );
        equal( repos.find( "li" ).eq( 2 ).css( "color" ), "rgb(255, 165, 0)" );
        equal( repos.find( "li" ).eq( 3 ).css( "color" ), "rgb(255, 165, 0)" );
        equal( repos.find( "li" ).eq( 4 ).css( "color" ), "rgb(255, 0, 0)" );
        start();
    });
});

The test starts by mocking our call to GitHub's JSON API using mockjax to provide a custom response. We tailor the response so that the data hits the edge cases for our coloring logic.

We then invoke our loadRepos() method from earlier. When the mock call returns, we ensure that the list of repositories was filled and colored correctly with a few assertions.

These tests give us a repeatable way to verify our coloring logic works as intended. Now we just need to run them.

Running the Tests

Running a QUnit test suite is as easy as loading its .html file in a browser. But since PhoneGap/Cordova applications load the app's root index.html page, and our tests are at tests/index.html, how do we access the test suite?

Unfortunately there's no good answer to this question. The best way I've come up with is to manually insert a link on the app's index.html page.

<a href="tests/index.html">Run Tests</a>

If you load the application and click this link the test runner loads and the tests run. The results are shown below.

View of the test runner on an iOS device View of the test runner on an Android device

The nice thing about running tests manually is they run on the actual device that will run the app. If there's a bizarre iOS or Android bug you need to test against, you'll hit it.

The downside of course is that is a manual process. Who wants to manually insert a link and run the suite every time you make a change. Luckily, there are ways to automate the test suite.

Automating the Test

One popular means of automating a browser based tests suite is PhantomJS. PhantomJS is a headless WebKit browser that you can control with a JavaScript API. This means that we can run our tests from the command line, and even incorporate them into a CI server or git pre-commit hook.

To run our tests from the command line we need to do the following:

  1. Download and install PhantomJS.
  2. Download runner.js from QUnit's official PhantomJS Runner.
  3. Place runner.js in the app's tests directory.
  4. Run the following command from the tests directory: phantomjs runner.js index.html

You'll see the following output.

Output of the tests on the terminal

What this is doing is pretty cool when you think about it. Under the hoods, the test suite is loaded in a headless browser, the tests execute, and the results are reported back on the command line. And for our sample app it all happened in a half second.

If you're a Grunt user, then this process is even easier. All you have to do is install the grunt-contrib-qunit plugin, and add the following to your Gruntfile.

module.exports = function( grunt ) {
    grunt.initConfig({
        qunit: {
            all: [ "tests/*.html" ]
        }
    });

    grunt.loadNpmTasks( "grunt-contrib-qunit" );
};

Then you can use grunt qunit to run your tests. Under the hoods, the grunt-contrib-qunit task is using PhantomJS to actually run the tests; therefore the results are the same. However, you do get slightly nicer output by default.

Output of the tests on the terminal

It's important to remember that while running our tests with PhantomJS is convenient, it is not the same as running them on a real Android or iOS device. Therefore it is possible that tests that pass in PhantomJS could fail on a device.

That being said, since the web views in Android and iOS are based on WebKit, the environments are extremely similar. For most applications, the power of automating tests outweighs the environment concerns. You can always manually test on devices as needed.

Note: Android 4.4 complicates the environment concerns some as the default web view is now based on Chrome, which uses Google's Blink rendering engine. However, Blink is still a recent fork of WebKit; therefore the differences between the two are still small.

Wrapping Up

In this article we saw how to setup and run a QUnit test suite for a PhoneGap/Cordova application. We also saw how to automate the tests with PhantomJS and incorporate them into a Grunt workflow.

The full source for the example app is available at https://github.com/tjvantoll/QUnit-Tester. For quick testing you can clone the project directly in Icenium. If you have any questions, please let us know in the comments.


About the Author

TJ VanToll

is a web developer, speaker, and writer living in Lansing, MI and working as a developer advocate for Telerik. He is an open source advocate that is on the jQuery UI team and publishes his blog open source. TJ is also the author of an upcoming book from ManningjQuery UI in Action. When not online, TJ is generally found chasing his twin sons in circles. TJ is @tjvantoll on Twitter and tjvantoll on Github.

Related Posts

Comments

Comments are disabled in preview mode.