2009-07-14

Stay green: Javascript Unit Testing

With the improvement of our source code quality as a constant goal, we decided to find a way to write Unit Tests for our Javascript code. Our implementation of Apache Commons Lang Validate for Javascript was our first step to build more solid applications, but an important part was missing until now: unit testing. We found a way to integrate JS unit tests with Maven, Eclipse, and the continuous integration server Hudson.

Zauber Javascript Validation Utils
Is an open source library inspired on org.apache.commons.lang.Validate written by Zauber and published under the Apache License v2.0. You can download it from
  • https://code.zauber.com.ar/repos/sandbox/components/javascript/validate/code/releases/0.2/
  • How we test javascript code?
    We use an open source framework called JSUnit1.3, a port of JUnit. There are two frameworks with the same name (JSUnit), the one we are using, JSUnit1.3, uses Rhino to run the tests. The second one, JSUnit2, uses a HTML file called testRunner.html to run the tests on multiple browsers from client-side. This is great to do cross-browser compatibility tests, but for the moment we are just validating Javascript syntax and logic using the first toolkit.
    Project structure
    The main idea is to respect Maven's conventions, so our project structure looks like this:
       /app
          /src
           * /main
               o /java
               o /javascript
                   + validate.js (the library)
               o /resources
           * /test
               o /java
               o /javascript
                   + validateTest.js (tests for validate.js written in JavaScript)
               o /resources
           * /target
               o /surefire-reports
                   + TEST-JS-Validate.xml
           * pom.xml
     
    Configuration - pom.xml
    This file is the key for a successful configuration, you should declare the junit plugin for maven2, something like this:
       <plugin>
           <groupId>de.berlios.jsunit</groupId>
           <artifactId>jsunit-maven2-plugin</artifactId>
           <version>1.3</version>
           <extensions>true</extensions>
       </plugin>
       
    and then configure your tests, for example:
       <executions>
            <execution>
              <phase>test</phase>
              <goals>
                <goal>jsunit-test</goal>
              </goals>
              <configuration>
             
                 <!-- src -->
                 <sourceDirectory>src/main/javascript</sourceDirectory>
                 <sources>
                   <source>validate.js</source>
                 </sources>
                 
                 <!-- test -->
                 <testSourceDirectory>src/test/javascript</testSourceDirectory>
                 <testSuites>
                   <testSuite>
                     <name>JS-Validate</name>
                   </testSuite>
                 </testSuites>
                
                 <!-- report -->
                 <reportsDirectory>target/surefire-reports</reportsDirectory>
               </configuration>
             </execution>
       </executions>
       
    That's all, you can now run your JS test just calling maven as usual (ie. mvn test).
    $ mvn test
    [INFO] Scanning for projects...
    [INFO] ------------------------------------------------------------------------
    [INFO] Building Validation Utils
    [INFO]    task-segment: [test]
    [INFO] ------------------------------------------------------------------------
    [INFO] [resources:resources]
    [INFO] Copying 0 resource
    [INFO] [compiler:compile]
    [INFO] Nothing to compile - all classes are up to date
    [INFO] [resources:testResources]
    [INFO] Copying 0 resource
    [INFO] [compiler:testCompile]
    [INFO] Compiling 1 source file to: target/test-classes
    [INFO] [surefire:test]
    [INFO] Surefire report directory: target/surefire-reports
    ------------------------------------------------------
    T E S T S
    -------------------------------------------------------
    There are no tests to run.
    
    Results :
    Tests run: 0, Failures: 0, Errors: 0, Skipped: 0
    
    [INFO] [jsunit2:jsunit-test {execution: default}]
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESSFUL
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time: 8 seconds
    [INFO] Finished at: Tue Jul 14 10:43:00 ART 2009
    [INFO] Final Memory: 18M/148M
    [INFO] ------------------------------------------------------------------------
    
    Note that maven says There are no tests to run but jsunit-test is being called.
    Configuration - Eclipse
    There is also another way to run your tests: calling them from Eclipse just like you do with regular Java tests. To do that, you could write your own implementation or include a module from Zauber Commons 3.16 called commons-javascript-test. We use the class ar.com.zauber.commons.test.javascript.JsTestCase. This class extends from JUnit TestCase, so it can be used just like a regular test case.
    So, how can we run it from Eclipse? A very simple example, step by step:
    1. write some Javascript code, like we did in validate.js
    2. write some tests for that code, you should follow this prototype
            function ValidateTest(name) {
                TestCase.call( this, name );
            }
            
            ValidateTest.prototype = new TestCase();
            
            //"import" validate class
            Validate = validate.Validate;
            
            /** 
             * this function tests Validate.notNull with many valid values and a null value 
             */
            ValidateTest.prototype.testNotNull = function(){
               var valids = new Array(0, 1, true, false, 'text', new Object(), 
                       new Array(1,2), function(){});
               var invalid = null;
            
               for(i in valids){ // tests all valid values
                   try{ 
                       Validate.notNull(valids[i]); // testing a not null value, shoudn't fail.
                   }catch(e){
                       fail("notNull shouldn't raise an exception with value:" + valids[i] + " (type: " + typeof(valids[i]) + ").");    
                   }
               }
            
               try{
                   Validate.notNull(invalid); // testing a null value, should fail.
                   fail("notNull should raise an exception with a null value.");
               }catch(e){
                   //notNull is working correctly
               }
            }
         

      Here you can use the code written in step one by "importing" it, just like we do in this example.

    3. Write a Java Class extending from JsTestCase, it will force you to implement a method called getIncludes(). That method returns a String array with the path for all files to be included in the test context, and all files that actually contain the tests. There is an important point here, and it is the name of this Java class. Normally you don't want this class to be executed by maven, so the class name should not end with Test, any other name will work, for example, we like to use Driver:
          public class ValidateDriver extends JsTestCase {
         
           /** @see JsTestCase#getIncludes() */
           @Override
           protected final String[] getIncludes() {
               return new String[] {
                   "src/main/javascript/validate.js",
                   "src/test/javascript/ValidateTest.js"
               };
           }
         
    4. Now, just click on Run As JUnit Test and you will see your tests running.
    5. Optionally, you can check the test reports on standard output. This can be achieved over all your tests by passing the following argument to the JVM: -Djavascript.test.showdetails=true or over an individual test by overriding the method showDetails() to return true.
    A working example
    You can checkout this project from our svn repository at https://code.zauber.com.ar/repos/sandbox/components/javascript/validate/code/releases/0.2/

    We hope you like it and use it ! Any feedback is always welcome.

    1 comments: