Thursday, November 11, 2010

TDD with javascript, JQuery, QUnit and Maven

I recently attended a Test Driven Development (TDD) training by Brett Schuchert of ObjectMentor.com and find it really useful! With TDD your code maintenance and evolution are easier and regression testing is less likely to have bugs because it prevents bugs from happening in the first place.

But what is TDD exactly? The instructor defined it as a "design practice". It uses tests as mechanism for discovery and feedback. He also added that TDD is not always the right thing to do. It depends on your application and requirements.

I was surprised to know that TDD idea has been around since late 50's. The original Mercury Rocket Project uses TDD.

The training inspired me and had me started looking on the how to apply TDD in javascript. The instructor advised some framework such as jsunit, which is good, but since our FEDs (Frontend developers) uses JQuery, I have to find a framework that has less learning curve and easy to use.

I searched and found out about Qunit (http://docs.jquery.com/Qunit). QUnit is a powerful, easy-to-use, JavaScript test suite. It's used by the jQuery project to test its code and plugins but is capable of testing any generic JavaScript code (and even capable of testing JavaScript code on the server-side).

Qunit is cool! But I have to integrate it with our maven project. I want our "maven build" to break if javascript unit test fails. (like how JUnit works ).

After further research, I found Rhino. Rhino is an open-source implementation of JavaScript written entirely in Java. It is typically embedded into Java applications to provide scripting to end users.

Given these 2 technologies, I configured maven to use qunit and Rhino to do javascript unit testing, and here's how I did it. ( If you have a better solution or comments, please let me know. )

One thing to keep in mind, when using this combo is that, you have to separate your javascript calculation logic from DOM-manipulation logic. You can extract them to a function or to an object, and unit test them separately. Use selenium to test the DOM-manipulation part. The reason being is that, you are executing javascript without the browser, and DOM-manipulation logic is easier to test using Selenium.

Here's an example on how to do it.

1) adder.js - This file is where you put the functions that you want to test.

//function that adds.
function adder(x,y) {
    return x + y;
}

2) adderTest.js - This file is your unitTest.
test("Adding numbers works", function() {
        expect(2);
        ok(adder, "function exists");
        equals(4, adder(2, 2), "2 + 2 = 4");
            }
);

3) suite.js - This file is your suite. It can contain many test.js files.

load("src/main/webapp/WEB-INF/js/qunit/qunit.js");

QUnit.init();
QUnit.config.blocking = false;
QUnit.config.autorun = true;
QUnit.config.updateRate = 0;
QUnit.log = function(result, message) {
    if(result == false) {
        print("FAILED: " + message);
        java.lang.System.exit(0);
    }else {
       print("PASS: " + message) ;
   }

};

load("src/main/webapp/WEB-INF/js/adder.js");
load("src/main/webapp/WEB-INF/js/adderTest.js");


NOTE:
a) Make sure you have downloaded qunit, and put it in your js directory. It is being referenced by suite.js
b) since I am using Maven 2, the path should starts from your base directory. (In my setup, it's in src/main/webapp/WEB-INF/js)

4) Add a dependency on Rhino and a maven plugin in your pom.xml:

...
<dependency>
   <groupid>rhino</groupid>
    <artifactid>js</artifactid>
    <version>1.7R1</version>
</dependency></pre>

...

<plugin>
    <groupid>org.codehaus.mojo</groupid>
    <artifactid>exec-maven-plugin</artifactid>
    <version>1.1</version>
    <executions>
        <execution>
        <phase>test</phase>
        <goals>
            <goal>java</goal>
        </goals>
        </execution>
    </executions>
    <configuration>
        <mainclass>org.mozilla.javascript.tools.shell.Main</mainclass>
        <arguments>
            <argument>-opt</argument>
            <argument>-1</argument>
            <argument>${basedir}/src/main/webapp/WEB-INF/js/suite.js</argument>
        </arguments>
    </configuration>
</plugin>
 



Now, when you execute "mvn test", it will execute the test suite, and run your test cases. Try adding this line in your addTest.js, and make sure to update the expect() method call.

equals(10,newAddition(5,0), "5 + 0 = 5");


making it:
test("Adding numbers works", function() {
     expect(3); //  <-- change to 3.
     ok(adder, "function exists");
        equals(4, adder(2, 2), "2 + 2 = 4");
        equals(10,adder(5,0), "5 + 0 = 5");
            }
);
);
It will break your maven build and tell you:

PASS: function exists
PASS: <span class="test-message">2 + 2 = 4</span>
FAILED: <span class="test-message">5 + 0 = 5</span>