Unit testing Javascript with phantomjs, and jasmine

JavaScript is a powerful scripting language that couples HTML markup and functional event-driven scripting, into rich and extremely versatile web applications. Unit testing JavaScript is a relatively new field, and in this article I will dive a little deeper into the tools I used in my recent JavaScript project: phantomjs and jasmine

Client vs Server

I believe that JavaScript is a powerful tool for serious web developers; however, well written applications in PHP, are much easier to develop, debug, test, deploy, and maintain. When you choose to develop an application or a component in JavaScript, you must take into account that your code may behave differently in different browsers, operating systems, and user settings. Unfortunately, this article does not cover those
compatibility tests, and I have yet to uncover a way to do this properly, but rest assured the day will come and Internet Explorer will be completely up to standard 🙂

Reluctancy to unit-test JavaScript

Before I dive into the howto, I must say that I was highly skeptical any of this would work on my existing code, and I failed to see the added value in spending a week on researching JS unit testing. When I was first asked to write unit-tests for a JavaScript application, I thought to myself that it must be a prank of some sort. I did not believe that testing JavaScript was even possible. All those DOM elements and event listeners, and the sheer fact that JavaScript is asynchronous, a language that runs in a browser, how on earth would I test this? After learning some more about phantomjs, and writing the infrastructure along with about two hundred unit-tests, I can definitely say that it was the right thing to do. Unit-testing JavaScript is essential in saving precious time debugging and miserably yanking hair out of own’s head in despair.

OK folks, lets get down to business!

Installing phantomjs

Phantomjs is a headless browser, and is completely opensource. It is the main program to run when you want to execute your tests. It supports loading of remote or local web pages, scripts, stylesheets, images, and rendering web pages into images (screen capture). You can also inject scripts, connect to databases or APIs, etc.

I am running a MacOS mountain lion and I installed phantomjs using ‘Homebrew’. There’s an .exe installer for windows, and ubuntu users can ofcourse apt-get it.

Here is the link to their download page just in case: http://phantomjs.org/download.html

Once you have installed phantomjs, you can test it by writing “Hello World”. Just create a file called hello.js with the following contents:

console.log('Hello, world!');
phantom.exit();

Now run:

phantomjs hello.js

As you can see, the behavior is very similar to your browser’s JavaScript console. Feel free to familiarize yourself with the phantomjs API and browse some examples.

Downloading Jasmine

Jasmine is a javascript framework for testing and reporting. It is completely independent, and works on its own without the need for any other library. Be sure to grab the latest version from the creator’s site, I used version 1.2.0 in my testing. Here is the link to the Jasmine download page: https://github.com/pivotal/jasmine/downloads In addition to the jasmine code, you will need console-reporter plugin for jasmine, which I found on github and you can get it here https://github.com/jcarver989/phantom-jasmine/blob/master/lib/console-runner.js

Creating your first test

To make it all work together, we will create a demo application consisting of two files: an index.html and app.js. The app should also include the jquery library The index.html file will consist of a div and a button

<!DOCTYPE html> 
<html> 
    <head> 
        <title>My first unit test</title>         
        <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js">
        </script> 
        <script src="app.js"></script> 
    </head> 
    <body> 
        <div id="content" style="color: #000000">This text is black and changes to white after clicking the button</div> 
        <button id="change">Change color</button> 
    </body> 
</html>

 

The app.js file will have a single function that manipulates the text color of the #content div once the #change button is clicked

function changeColor() {
    console.log("running animation");
    $('#content').animate({color: #FFFFFF}, 1000);
}; 

$('#change').live('click', function(){
    changeColor();
});

If you run this in your browser you will see a button and a some text. Once you click the button, the color of the text should change. Until now everything is working great. Now lets see how we can automate this process, and let the machine check if indeed this page is working or not.

Preparing the phantomjs script

Phantomjs needs a few scripts in order to know what to check. Lets prepare a quick script that loads index.html, injects the jasmine libraries and the testing spec itself (we will write that one later). Lets call this file “runtest.js”

/* runtest.js */ 
var page = require('webpage').create(); 
var fs = require('fs'); 
var index = 'file:///' + fs.workingDirectory + '/index.html'; 
// load the index.html 
page.open(indexfile, function(status) { 
    if (status !== 'success') { 
        console.log('Unable to load the index' + index); 
    } else { 
        window.setTimeout(function() { 
            // once loaded, we can inject the needed javascripts 
            // make sure to have jasmine.js in the same folder 
            page.injectJS('jasmine.js'); 
            // inject console runner and the tests themselves 
            page.injectJS('console-runner.js'); 
            page.injectJS('specs.js'); 
            // init the console reporter and execute the tests 
            // from jasmine 
            var console_reporter = new jasmine.ConsoleReporter(); 
            page.evaluate(function(){ 
                jasmine.getEnv().addReporter(new jasmine.TrivialReporter()); 
                jasmine.getEnv().addReporter(console_reporter); 
                jasmine.getEnv().execute(); 
            }); 
        }, 200); 
    } 
}); 
// handle console messages and the end of testing 
page.onConsoleMessage = function(msg) { 
    if(msg === "ConsoleReporter finished") { 
        phantom.exit(); 
    } 
    // the pages open in a sandbox, so in order for console 
    // messages to reach us we need catch them and to 
    //  pass them along 
    return console.log(msg); 
};

This is it for the running script. The only thing left to do, is to create the actual tests. Create a file called specs.js and include your tests there in the following manner:

/* specs.js */
describe("Testing the page", function() {
    it("will change color after the click", function() {
        // emulate a click on the button
        $('#change').click();
        // see that the color is no longer #000000
        expect($('#content').css('color')).not.toEqual('#000000');
    });
});

Next from your console or command line, run:

phantomjs runtest.js

This test will prove that the button works, but not entirely. Our button animates the color of the text, and the duration of this animation is 1000 milliseconds. We will add another test to our already existing test. This test will check asynchronously for the changes in the color.

/* specs.js */
describe("Testing the page", function() {
    it("will change color after the click", function() {
        // emulate a click on the button
        $('#change').click();
        // see that the color is no longer #000000
        expect($('#content').css('color')).not.toEqual('#000000');
    });

    it("the color animated to #FFFFFF", function() {
        // with the combination of "runs" and "waitsFor"
        // functions of the jasmine framework, we can
        // easily achieve this goal
        // the test loops around until color is #FFFFFF
        waitsFor(function() {
         return $('#content').css('color') == '#FFFFFF';
        }, "Color never turned #FFFFFF", 2000);

        runs(function() {
           expect($('#content').css('color'))
                                      .toBe('#FFFFFF');
        });
    });
});

Run the tests again

phantomjs runtest.js
Et voilà! Two successful specs executed!

To conclude

The example I’ve put together in this post is very basic, but I am certain that it will be a great start for anyone who is about to dabble in JavaScript unit-testing. There are many ways to test JavaScript, and many frameworks out there that are as good, or maybe even better than what I’ve used in these simple examples. I am sure your journey for knowledge will be as exciting as mine. For any questions, or comments, please contact me at leon@panda-os.com

Leon Gordin
Co Founder, PandaOS

2 thoughts on “Unit testing Javascript with phantomjs, and jasmine

Leave a Reply

Your email address will not be published. Required fields are marked *