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.
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.

