I'm currently working on a pairing project with Nicole, a fellow 8th Light apprentice, where we are developing an internal tool intended for rating how well people review projects on three criteria: whether they are kind, specific, and actionable. (While the obvious use case is for evaluating the helpfulness of code reviews, it could conceivably be used for any endeavor where feedback is solicited, including blog posts or event planning.) We are building the application in Ruby and Rails, but there are several features that require Javascript to mediate user interactions. Most of these are fairly simple uses of jQuery and AJAX to display a form or error message dynamically on the same page without redirection or reloading. But testing our Javascript turned out to be more difficult than we anticipated.
We used Jasmine as our testing framework, but Jasmine by itself is not sufficient to cover asynchronous AJAX requests and DOM manipulation. We spent quite a lot of time navigating Stack Overflow responses and asking for help from coworkers, which led us to wrestle with jasmine-ajax and jasmine-jquery. Both of these plug-ins were significantly overpowered for what we were trying to test, and we got nowhere while trying to figure out how to use them correctly.
Luckily, my mentor got back from vacation, and I was able to pick his brain for assistance. He suggested using sinon and jasmine-fixture, which were both fairly simple libraries with documentation that was easy to decipher.
The first step was to separate the AJAX calls themselves from the DOM
manipulation. I set up my tests using sinon
's handy fakeServer
and spy
classes. I told the fake server to give a 200 status response to the type of
AJAX request I was testing and a spy to watch that the AJAX call was made and
successfully completed.
Now to test the DOM manipulation. I had tried to set up HTML fixture files while
experimenting with jasmine-jquery
, but that seemed like a lot of redundant code
for testing functions that were pretty much just displaying or hiding elements
on a page. The affix()
function from jasmine-fixture
was much simpler,
allowing you to quickly set up the elements you needed to test, while also
taking care of cleaning them up after the test was run. I could create an
element, call my DOM manipulation function, then check to see that necessary
change had happened to the element.
These two steps allowed me to create a toolkit of reusable functions for making AJAX GET and POST requests and for displaying, hiding, and replacing parts of the DOM. Then I could easily write unit tests for the specific user interactions that combined those functions. Having the fake server here was especially useful because you could explicitly test that an element was loading the message body of the response received from the server after an AJAX request.
We are using Capybara to handle our
acceptance tests. While Capybara is very powerful, you do need to be thoughtful
about how you write any tests that cover functionality involving asynchronous
requests. One blog
post provides a good summary of the points to keep in mind. However,
something very simple that no blog post or Stack Overflow answer seemed to
explicitly cover is that you need to include :js => true
in your test blocks
for Rspec to realize it needs to use the Javascript driver.
Speaking of which, it probably helps to use a headless browser instead of the Capybara default for the Javascript driver. I ended up going with PhantomJS through the Poltergeist gem, since we are using Travis CI for continuous integration, and it has PhantomJS already installed.
Finally, note that if your acceptance test involves any interaction with hidden elements
on your page, Capybara may require you to explicitly set
Capybara.ignore_hidden_elements = false
(depending on your version).
The source code for our project, still in progress, is available on Github, and a demo is deployed on Heroku.