Showing posts with label JavaScript. Show all posts
Showing posts with label JavaScript. Show all posts

August 2, 2011

Maven + JavaScript Unit Test: Running in a Continuous Integration Environment

JavaScript Unit Testing Continued

So you're still interested in unit testing JavaScript (good). This post is an extension of my much more indepth first posting on how to unit test JavaScript using JS Test Driver. Please check it out here.

Recap Last Posting

In the last posting we successfully unit tested JavaScript using Maven and JsTest Driver. This allowes us to test JavaScript when on an environment that has a modern browser installed and can be run.

Problem with typical CI environments

So what happens when the test are passing on your local box, but you go to check in your code and the Continuous Integration (CI) server pukes on the new tests becasue there is no "screen" to run chrome or firefox? As of this posting, none of the top-tier browsers have a "headless" or an in-memory only browser window. There are alternatives to running JavaScript in a browser, such as rhino.js, env.js or HtmlUnit, however, these are just ports of browsers and the JavaScript and DOM representation are not 100% accurate which can lead to problems with your code when rendered in a client's browser.

Approach

What we need to do is to run JSTestDriver's browser in a Virtual X Framebuffer (Xvfb) which is possible on nearly all Linux based systems. The example below uses a Solaris version of Linux, however, Debian and RedHat linux distrubutions come with the simplified bash script to easily run an appliation in a virtual framebuffer. This solution was derived from one posted solution on the JS Test Driver wiki (http://code.google.com/p/js-test-driver/wiki/ContinuousBuild. The given example is also a full working example that is in use at my current client.

Here is the quick list of what we will accomplish. Note, several of these steps are discussed in depth in the previous post and are not covered in depth here.
  1. Create a profile to run Js Unit-Tests
  2. Copy JsTestDriver library to a known location for Maven to use
  3. Copy JavaScript main and test files to known locations
  4. Use ANT to start JsTestDriver and pipe the screen into xvfb

Here is a sample profile to use. You will need to adjust the properties at the top of the profile to match your system.
<profile>
    <!-- Runs JS Unit Test inside of Xvfb -->
    <id>ci-jstests</id>
    <properties>
        <firefox.location>/opt/swf/bin/firefox</firefox.location>
        <js-test-driver.version>1.3.2</js-test-driver.version>
        <xvfb.location>/opt/X11R6/xvfb-run</xvfb.location>
    </properties>
    <build>
        <plugins>
            <!-- Copy JS Test Driver JAR to target folder -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.1</version>
                <executions>
                    <execution>
                        <id>copy</id>
                        <phase>generate-resources</phase>
                        <goals>
                            <goal>copy</goal>
                        </goals>
                        <configuration>
                            <artifactItems>
                                <artifactItem>
                                    <groupId>com.google.jstestdriver</groupId>
                                    <artifactId>jstestdriver</artifactId>
                                    <version>${js-test-driver.version}</version>
                                    <type>jar</type>
                                    <overWrite>true</overWrite>
                                    <destFileName>jsTestDriver.jar</destFileName>
                                </artifactItem>
                            </artifactItems>
                            <outputDirectory>${project.build.directory}/jstestdriver</outputDirectory>
                            <overWriteReleases>false</overWriteReleases>
                            <overWriteSnapshots>true</overWriteSnapshots>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <!-- Copy JavaScript files and Tests files  -->
            <plugin>
                <artifactId>maven-resources-plugin</artifactId>
                <version>2.4.3</version>
                <executions>
                    <execution>
                        <id>copy-main-files</id>
                        <phase>generate-test-resources</phase>
                        <goals>
                            <goal>copy-resources</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/test-classes/main-js</outputDirectory>
                            <resources>
                                <resource>
                                    <directory>src/main/webapp/scripts</directory>
                                    <filtering>false</filtering>
                                </resource>
                            </resources>
                        </configuration>
                    </execution>
                    <execution>
                        <id>copy-test-files</id>
                        <phase>generate-test-resources</phase>
                        <goals>
                            <goal>copy-resources</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/test-classes/test-js</outputDirectory>
                            <resources>
                                <resource>
                                    <directory>src/test/webapp/scripts</directory>
                                    <filtering>false</filtering>
                                </resource>
                            </resources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-antrun-plugin</artifactId>
                <version>1.6</version>
                <executions>
                    <execution>
                        <phase>test</phase>
                        <goals>
                            <goal>run</goal>
                        </goals>
                        <configuration>
                            <target>
                                <mkdir dir="${project.build.directory}/js-test-results" />
                                <exec executable="${xvfb.location}">
                                    <arg line="java" />
                                    <arg line="-jar ${project.build.directory}/jstestdriver/jsTestDriver.jar" />
                                    <arg line="--port 4220" />
                                    <arg line="--testOutput ${project.build.directory}/js-test-results" />
                                    <arg line="--config ${project.build.directory}/test-classes/jsTestDriver.conf" />
                                    <arg line="--browser ${firefox.location}" />
                                    <arg line="--tests all" />
                                    <arg line="--verbose" />
                                </exec>
                            </target>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</profile>

Possible problems

Although I cannot predict or fix all problems, I can share the one major problem I ran into with Solaris and the script used to fix that. In Solaris (and could happen to other distros) the xvfb-run script was not available and several of the other libraries did not exist. I first had to download the latest X libraries and place them in their appropriate locations on the CI server. Next, I had to re-engineer the xvfb-run script. Here is a copy of my script (NOTE: This is the solution for my server and this may not work for you)

I created a script that contains:

/usr/openwin/bin/Xvfb :1 screen 0 1280x1024x8 pixdepths 8 24 fbdir /tmp/.X11-vbf &

May 31, 2011

Maven + JavaScript Unit Test using JsTestDriver

What are we doing?
You wake up to the annoying Justin Timberlake ringtone you set for the office only to discover it's the site is on fire" call. A quick glance at the alarm clock and you realize it's 3am. It's this time when you decide that you need to get some test coverage on your Java Script.

What is Unit Testing?
Unit Testing is a method by which individual units of source code are tested to determine if they are fit for use. A unit is the smallest testable part of an application and in JavaScript, typically it is a function.

Why would you want to unit test JavaScript?
JavaScript has grown from the little scripts that would change button images on rollover to a fully functional, and often application critical, complex applications. There are many interactions with DOM objects, AJAX request/response and user events that make testing your code a must-have. Unit testing will give you the the confidence to add new features and change existing code. In addition, unit test self-document the code so you can be sure that the rad new function you wrote does exactly what you want it to.

What is covered
What the rest of this blog will describe is one approach to integrate maven and JsTestDriver for local development. In a near future blog, this approach will be extended to use on a Linux/Unix based system for a continuous integration system.

Approach
There are many approaches to unit testing JavaScript (QUnit being one of the many), however, most of the unit test frameworks do not easily allow you to integrate your unit test as a part of a maven testing cycle. The one problem with all JavaScript unit testing frameworks is that you need to have a place to run your test. At the time of this blog, there is not a "headless" browser, thus you (or your framework) must open a browser, accept JavaScript loading and running the test cases while providing repeatable testing output. This blog series will discuss a few different methods to run unit test, including a method to run in a virtual framebuffer for continuous integration systems that do not allow you to open a browser in the default display.

What is JsTestDriver?
JsTestDriver is an small application that controls your test cases, your source files and the interaction between the browsers you choose to test against. JsTestDriver allows you to only load the source files you want, along with only the DOM elements you need to unit test your source code, in other words, the browser ONLY loads your JavaScript files and optionally HTML and/or DOM elements needed by your test.

How to integrate with Maven
The approach here is to add JavaScript testing via a profile, so you can create a CI build specifically for your JavaScript unit test.

  1. JsTestDriver Setup

    This blog will not discuss in depth how to setup and run JsTestDriver. For more in-depth information, please visit http://code.google.com/p/js-test-driver/ website. This blog is using JsTestDriver 1.3.2.

    In order to load your test scripts, you must create a configuration file. For a maven application, place your configuration file in your "/resources" folder.

    Here is an example properties file (jsTestDriver.conf)

    server: http://localhost:4220
    
    load:
     # Header JS Files, all files are loaded top-down
     - main-js/lib/jquery.min.js
     # Body JS files
     - main-js/Person.js
     # Test files
     - test-js/*.js
    
    exclude:
     - main-js/exclude.js
    
    

    The above configuration file assumes your file structure is similar to:
    /src/main/webapp/scripts/*.js
    /src/test/webapp/resources/jsTestDriver.conf
    /src/test/webapp/scripts/*.js

    (NOTE: You might notice that the file path is different than the "main-js". Keep reading!)

  2. Maven Dependencies and Profile

    Next, let's add the dependencies to the POM file in your project.

    Add the following to your "dependencies" section in your pom.xml
    <dependency>
     <groupid>com.google.jstestdriver</groupId>
     <artifactid>jstestdriver</artifactId>
     <version>1.2.2</version>
     <scope>test</scope>
     <type>jar</type>
    </dependency>
    
    

    Next, we need to add a profile, which will do quite a few things. First, the JsTestDriver jar file will be copied from the repository to a known folder in the /target folder for use when running the JsTestDriver server. Next, we need to copy the JavaScript files from your project's location to a known folder in the "/test" folder, and also to match the "main-js/*" and "test-js/*" folder structures that we setup in the jsTestDriver.conf file. Lastly, we need to startup the JsTestDriver server so we can run our test.

    Here is a profile to start with:

    <profile>
        <id>local-jstests</id>
        <build>
            <plugins>
    
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-dependency-plugin</artifactId>
                    <version>2.1</version>
                    <executions>
                        <execution>
                            <id>copy</id>
                            <phase>generate-resources</phase>
                            <goals>
                                <goal>copy</goal>
                            </goals>
                            <configuration>
                                <artifactItems>
                                    <artifactItem>
                                        <groupId>com.google.jstestdriver</groupId>
                                        <artifactId>jstestdriver</artifactId>
                                        <version>${js-test-driver.version}</version>
                                        <type>jar</type>
                                        <overWrite>true</overWrite>
                                        <destFileName>jsTestDriver.jar</destFileName>
                                    </artifactItem>
                                </artifactItems>
                                <outputDirectory>${project.build.directory}/jstestdriver</outputDirectory>
                                <overWriteReleases>false</overWriteReleases>
                                <overWriteSnapshots>true</overWriteSnapshots>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
    
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <configuration>
                        <skipTests>true</skipTests>
                    </configuration>
                </plugin>
    
                <plugin>
                    <artifactId>maven-resources-plugin</artifactId>
                    <version>2.4.3</version>
                    <executions>
                        <execution>
                            <id>copy-main-files</id>
                            <phase>generate-test-resources</phase>
                            <goals>
                                <goal>copy-resources</goal>
                            </goals>
                            <configuration>
                                <outputDirectory>${basedir}/target/test-classes/main-js</outputDirectory>
                                <resources>
                                    <resource>
                                        <directory>src/main/webapp/scripts</directory>
                                        <filtering>false</filtering>
                                    </resource>
                                </resources>
                            </configuration>
                        </execution>
                        <execution>
                            <id>copy-test-files</id>
                            <phase>generate-test-resources</phase>
                            <goals>
                                <goal>copy-resources</goal>
                            </goals>
                            <configuration>
                                <outputDirectory>${basedir}/target/test-classes/test-js</outputDirectory>
                                <resources>
                                    <resource>
                                        <directory>src/test/webapp/scripts</directory>
                                        <filtering>false</filtering>
                                    </resource>
                                </resources>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
    
                <plugin>
                    <artifactId>maven-antrun-plugin</artifactId>
                    <version>1.6</version>
                    <executions>
                        <execution>
                            <phase>test</phase>
                            <configuration>
    
                                <target>
                                    <!-- make output folder -->
                                    <mkdir dir="${basedir}/target/js-test-results"/>
                                    <!-- start jsTestDriver server -->
                                    <exec executable="java">
                                        <arg line="-jar ${basedir}/target/jstestdriver/jsTestDriver.jar"/>
                                        <arg line="--port 4220"/>
                                        <!-- Possibly set this up to point to the same location of your surefire output -->
                                        <arg line="--testOutput ${basedir}/target/js-test-results"/>
                                        <arg line="--config ${basedir}/target/test-classes/jsTestDriver.conf"/>
                                        <arg line="--browser /opt/google/chrome/google-chrome"/>
                                        <arg line="--tests all"/>
                                        <arg line="--verbose"/>
                                        <arg line="--captureConsole"/>
                                        <arg line="--preloadFiles"/>
                                    </exec>
                                </target>
                            </configuration>
                            <goals>
                                <goal>run</goal>
                            </goals>
                        </execution>
                    </executions>
    
                </plugin>
            </plugins>
        </build>
    </profile>
    
    

  3. Sample Unit Test

    Now you need to have a sample JavaScript file and a corresponding unit test.

    Here is a simple JavaScript file (/src/main/webapp/scripts/Person.js)

    NAMESPACE = {};
     
    NAMESPACE.Person = function(initObj) {
     this.name = initObj.name;
     this.age = initObj.age;
     this.address = initObj.address;
     this.sex = initObj.sex;
     this.significantOther = initObj.significantOther;
     
     function setSignificantOther(person) {
      this.significantOther = person;
     }
     
     this.setSignificantOther = setSignificantOther;
    }
    

    And a sample test case might be: (/src/test/webapp/scripts/PersonTest.js

    TestCase("PersonTest", {
     testA:function(){
      expectAsserts(1);
    
       var peterGriffin = new NAMESPACE.Person({
        name: "Peter Griffin",
        age: 43,
        address: {
         address1: "31 Spooner Street",
         address2: "",
         city: "Quhog",
         state:"RI"
        },
        sex: "Male",
        significantOther: null
       });
       
       assertNotNull("The variable should not be null", peterGriffin);
      };
     }
    });
    
    
  4. Running your test

    In order to run your test, you can now type:

    mvn clean test -Plocal-js


Please stay tuned for the expansion to this on how to run in a virtual framebuffer for a CI environment (and of course, updates for found errors)