Unit Testing JavaScript With QUnit And Phing

Recently I’ve been using both Phing for my PHP builds, and QUnit for my JavaScript unit tests, so I’ve been looking for a way to run these QUnit tests when Phing builds my application.

By default Phing can run PHPUnit tests, but not QUnit. However Martin Jonsson has come to the rescue with his Qunit Phing Task.

QUnit Phing Tasks lets QUnit tests be run during a build and Phing can either pass or fail based on these results. As QUnit is written in JavaScript, QUnit Phing Task runs PhantomJS to call a JavaScript wrapper that passes the output back to PHP and Phing. PhantomJS is a headless JavaScript browser that can excute on a command line.

Using QUnit Phing Task

To run the QUnit Phing Task you need to install PhantomJS. I downloaded a binary and placed it in /usr/local/bin on my system.

You need to download the QUnit Phing Task. I placed the files in a local ./lib directory in my project.

You will also need to download QUnit. I placed the files in a local ./tests directory in my project.

In the ./tests directory I created an HTML file called runner.html. This is the page we need to call to run our tests.

<!DOCTYPE html>

<html>
        <head>
                <meta chatset="utf-8" />
                <title>Qunit Tests</title>
                <link rel="stylesheet" href="./qunit.css">
                <script src="./qunit.js"></script>
                <script src="./test.js"></script>
        </head>

        <body>
                <div id="qunit"></div>
        </body>
</html>

I also create a JavaScript file called ./test.js in ./tests to hold my JavaScript tests in.

test("testing for true", function() {
        ok(true, "true is true");
});

Finally, I create a build.xml for my Phing build.

<?xml version="1.0" encoding="UTF-8"?>
<project name="qunit example" basedir="." default="qunit">

        <path id="project.class.path">
                <pathelement dir="./lib/" />
        </path>

        <taskdef name="qunit" classname="QunitTask">
                <classpath refid="project.class.path" />
        </taskdef>

        <target name="qunit" description="JavaScript Unit Test">
                <qunit executable="DISPLAY=:0 /usr/local/bin/phantomjs" haltonfailure="true" runner="./lib/run-qunit.js">
                        <fileset dir=".">
                                <include name="tests/runner.html" />
                        </fileset>
                </qunit>
        </target>

</project>

In this I am specifying a path to my ./lib directory as this is where the QUnit Phing Task code is. The path is then used in the taskdeftask which is used to create the <qunit> task. Now when I call <qunit> in my qunit target I just pass in the location of the executable – in this case where PhantomJS is, whether Phing should halt if the QUnit tests fail, and where to find the the JavaScript that runs QUnit. Inside the task, we pass in where our runner pages are, as we only have the one in this example we include it by name tests/runner.html.

Running this gives us the following…

rob@lamp ~/qunitphing> phing
Buildfile: /root/qunitphing/build.xml

qunit example > qunit:

    [qunit] ./tests/runner.html: 1 tests of 1 passed, 0 failed

BUILD FINISHED

Total time: 0.3275 seconds

So we can see our tests have passed. Let’s change our test.js so that it fails.

test("testing for true", function() {
        ok(true, "true is true");
        ok(false, "false is true");
});

This test should fail, as false is not true and will fail the ok assertion.

rob@lamp ~/qunitphing> phing
Buildfile: /root/qunitphing/build.xml

qunit example > qunit:

    [qunit] 1 tests failed on: ./tests/runner.html
    [qunit] Failed test: testing for true (1, 1, 2)
Execution of target "qunit" failed for the following reason: /root/qunitphing/build.xml:13:41: QUnit tests failed

BUILD FAILED
/root/qunitphing/build.xml:13:41: QUnit tests failed
Total time: 0.3258 seconds

So we can see that Phing has failed our build due to the QUnit tests failing, which is what we want.

Summary

Although a very simple and contrived example, I hope this has shown you how to add JavaScript unit tests using QUnit to your Phing build.

Using Phing To Deploy A PHP Project

Deploying a PHP project to a production or even a staging environment isn’t always as simple as copying a directory. One project I have been working on recently has benefited from using Phing as a deployment system. To deploy my code, I just have to call a single build script, and let Phing do all the hard work.

So what is Phing? Well Phing is a tool based on Apache Ant, specifically designed to build PHP projects. It takes an XML file that defines specific tasks and executes them. It is designed to be platform independent so should be at home running on Windows as well as Unix based systems.

Phing is available using PEAR, and can be installed quickly using the following commands…

pear channel-discover pear.phing.info
pear install phing/phing

Hello World! In Phing

It wouldn’t be an overview without a Hello World example. Save the following as build.xml.



    
        
    

Now let’s run this, we just call phing on the command line, and we get something like this back.

rob@lamp /tmp/helloworld> phing
Buildfile: /tmp/helloworld/build.xml

helloworld > helloworld:

     [echo] Hello World!

BUILD FINISHED

Total time: 0.0487 seconds

rob@lamp /tmp/helloworld>

So what is going on? Well in the project element we have a default setting of “helloworld”. This tells Phing the target to call by default. We can call this on the command line as well, phing helloworld, so if we have multiple tasks we can create a target for each of those. As you can see we only have a target of “helloworld” in our example. Inside the target we have tasks. In this example, we have a single EchoTask that just prints out the message “Hello World!”.

Adding A Clean Up

There is so much more Phing can do that just print Hello World!. One of the common tasks you may have is to cleanup a deployment directory. Let’s assume you have a build directory, when you call phing clean you want this to be removed. We can add a simple task to do this to our build.xml.



    
            

As you can see, as well as adding a target called “clean”, I’ve also added a property called “builddir” with the value “./build”. I can access this later as variable, so when I call the DeleteTask, I can reference this value using ${builddir}, and Phing will delete the directory ./build.

Create an empty directory called build and run this using phing clean.

rob@lamp /tmp/helloworld> phing clean
Buildfile: /tmp/helloworld/build.xml

helloworld > clean:

     [echo] Clean...

BUILD FINISHED

Total time: 0.0493 seconds

rob@lamp /tmp/helloworld>

When you check the contents of your working directory you should have found the build directory has been deleted.

Adding A Deployment Target

Deployment is the area where Phing really comes into it’s own. Let’s create a target that creates a build directory then uploads the contents of that via FTP to a remote server.


    
    
    
    
        
            
            
            
            
            
        
    

    
    
        
            
        
    

    

We’re doing a few things here, first we are deleting the ${builddir}, and creating it from scratch to ensure it is clean. Then we are copying all the files in the current directory across, except for the build directory, any build file with any extension, the docs directory and the tests directory. Then we are FTP’ing up the contents of the ${builddir} using a FTPDeployTask. Assume I have already set a selection of FTP variables earlier using the same syntax as earlier when I defined ${builddir}.

OK, so that is useful, but there are other useful things that could be done as part of the process. We may want our JavaScript minified on production, but not when we are developing. We can add a task to do this as part of the deploy using the JSMinTask.

There are plenty of other tasks that can be usefully integrated, such as running phpcs to ensure your code is meeting coding standards, building your documentation using phpdocumentor, running your phpunit unit tests. If anything fails you can prevent the deployment and stop your site breaking unnecessarily.

Summary

This has just been a quick overview of how to use Phing. We’ve seen various simple tasks take place, and we’ve seen we can easily add more tasks to increase functionality and usefulness. In the real world adding tests to check your code before it gets deployed is essential and prevents silly errors hitting your production environment. Taking the time to write a build script will also save you time in the future when you come to make changes to your site and you can’t quite remember how your production environment was setup. Using Phing, you just have to run your build script.