Jan 8 2007

Testing Javascript in Rails
Comments Off on Testing Javascript in Rails

Testing is encouraged and expected in Rails, but most people fall short on good coverage of their Javascript code. We’ll go over how the Scriptaculous Unit Testing library provides a nice solution to testing custom classes you have written using the Prototype library.

Installation

The Scriptaculous unit testing library can be installed in your Rails application using the javascript_test plugin located in the Rails subversion repository. To install the plugin, navigate to the base directory of your application and run:

$ ./script/plugin install \
http://dev.rubyonrails.org/svn/rails/plugins/javascript_test

Once the plugin is installed, you will have a new generator script to create tests for your Javascript files. To create a test skeleton file for a file such as public/javascripts/cookies.js:

$ ./script/generate javascript_test cookies
  create  test/javascript
  create  test/javascript/cookies_test.html
}) %>

Now we have a special directory within test to store our javascript test files. Each test suite will be an HTML file that includes the framework necessary for the tests to run.

Writing Tests

If you take a look at test/javascript/cookies_test.html, you’ll see a familiar setup to Ruby’s Test::Unit library. There are the typical setup and teardown methods, along with a dummy test to get you started.

new Test.Unit.Runner({
 
  // replace this with your real tests
  setup: function() {
  },
 
  teardown: function() {
  },
 
  testTruth: function() { with(this) {
    assert(true);
  }}
 
}, "testlog");

For the cookies.js library I’m testing, I want to make sure that my code can set and access cookies with new methods that I’m adding to the document object. I’ll start out by setting the cookie, and then assert that when I retrieve it, it is the same value I set. I’ll also add a snippet to the setup method that makes sure the cookie is deleted before each test.

new Test.Unit.Runner({
  setup: function() {
    // clear out cookie
    var expires = "expires=Thu, 01-Jan-70 00:00:01 GMT"
    document.cookie = 'foo' + "=; " + expires + "; path=/";
  },
 
  // test getting/setting cookies
  testSettingAndAccessingCookies: function() { with(this) {
    assertEqual(undefined, document.getCookie('foo'));
 
    document.setCookie('foo', 'bar');
    assertEqual('bar', document.getCookie('foo'));
  }}
}, "testlog");

This obviously isn’t the greatest test coverage, but you get the idea. Adding new tests cases is as easy is adding additional methods that are prefixed with test.

There are many additional assertions beyond assertEqual(), including among others:

  • assert
  • assertNull
  • assertMatch
  • assertVisible

Check the source in plugins/javascript_test/assets/unittest.js
for the full list.

Running the Tests

Running the tests is done in one of two ways. The first way is the very cool test:javascripts rake task that is bundled with the plugin. This task will launch an instance of the WEBrick server, and attempt to start up Safari, Firefox, IE, and Konquerer. It will run the javascript test in each of the available browsers on your system.

$ rake test:javascripts
  (in /Users/derek/work/demo)
  /test/javascript/cookies_test.html on Safari: SUCCESS
  /test/javascript/cookies_test.html on Firefox: SUCCESS
  Skipping Internet Explorer, not supported on this OS
  Skipping Konqueror, not supported on this OS

The tests results will pop up in a nice red=fail, green=pass HTML interface. There is no backtrace information given, so it’s smart to make small test cases so that you can easily find any specific problems.

Test Results

While this is fine and dandy, it is rather slow during active development. The other testing method is to open the HTML test files directly in your browser and just hit refresh to rerun the tests.

There is one small setup you need to do for this to work. You need to make a symbolic link to the unit testing javascript library for your tests to use. From your Rails application base directory:

$ ln -s ../../vendor/plugins/javascript_test/assets/ \
test/javascript/assets

Manipulating DOM Elements

When you’re writing Javascript, rarely does the code work in a vacuum. Most of the time you are manipulating some type of element in the DOM tree. To test this type of interaction, you will obviously need some mock HTML source for the test to operate on. The convention in these type of tests is to just add this HTML right to the test file itself above your tests.

<!-- our test DOM structure -->
<div id="widget_1">
  <h2>States</h2>
  <ul style="display: none">
    <li>Arizona</li>
    <li>California</li>
  </ul>
</div>
 
<!-- our javascript test code -->
<script type="text/javascript">
// <![CDATA[
...
// ]]>
</script>

You will notice this text showing up on the HTML page as your tests run. Don’t sweat it because this is normal. Just watch for red.

Writing Timed Based Tests

When using javascript animation effects, a lot of features happen over a period of time. When testing the state of elements in javascript (such as whether they are visible/hidden), we will often need to wait for the effect to take place before we make our assertion. The library includes a helpful way of doing this using the wait() method.

new Test.Unit.Runner({
  setup: function() {
  },
 
  // get/set cookies
  testShowWidgetBody: function() { with(this) {
    var widget = new Widget('widget_1');
    assertHidden(widget.body);
 
    // widget.toggle takes 500ms seconds to complete
    widget.toggle();
 
    // wait 550ms before asserting that it worked
    wait(550, function() { with(this) {
      assertNotHidden(widget.body);
    }});
  }}
 
}, "testlog");

There is much more to this library, but that’s all for now folks. Hopefully this gives you a good start so that you’re well on your way to testing your Javascript code as diligently as you test your Ruby code.

Sorry, the comment form is closed at this time.