Cucumber and Celerity - testing unobtrusive javascript
In the world of javascript and ajax web apps I often find that writing applications supporting both javascript enabled and disabled is hard. For the sake of simplicity and due to deadlines I often write only javascript version of some of the features. But there are many situations where both scenarios should be supported.
The most important parts of app, especially the ones that must be crawled by google, should be written with unobtrusive style. And here comes the problem… I rarely see javascript testing and till today I haven’t done it myself. Celerity and cucumber (with help of culerity) can solve this problem. Here is short guide introducing this technique.
I will use Cucumber, celerity (which is HtmlUnit wrapper with API compatible with watir) and culerity. Culerity is a proxy between celerity and your app. Celerity requires JRuby and probably your app need MRI or REE – culerity resolves this problem.
Let’s get started.
You need to install JRuby in order to run celerity. After installing and adding jruby to your PATH install celerity gem (probably as a root):
jruby -S gem install celerity
Now you can create rails app and configure the environment:
rails culerity-example
sudo gem install cucumber rspec rspec-rails haml
# add config.gem "haml" to environment.rb
gem install langalex-culerity --source http://gems.github.com
cd culerity-example
./script/generate cucumber
./script/generate rspec
# now edit database.yml and set database options
rake db:migrate
rake features
rm features/step_definitions/webrat_steps.rb # cause we will be using celerity
At this point you should have cucumber configured and you should be able to run “rake features” with output similar to:
0 scenarios
0 steps
0m0.000s
Let’s add some tests! You will need step definitions and hooks. Culerity provides some basic step definitions and hooks which you can generate with ”./script/generate culerity” but I’ve changed them a bit for my needs, so you can find them on this example repository.
Copy those files to your app.
The first file is just rewrite of webrat steps and the second file adds hooks for firing celerity server and browser. Let me explain the hooks.rb file:
require 'culerity'
Before do
$server ||= Culerity::run_server
$browser = Culerity::RemoteBrowserProxy.new $server, {:browser => :firefox}
$browser.webclient.setJavaScriptEnabled(false)
@host = 'http://localhost:3001'
end
Before("@js") do |scenario|
$browser.webclient.setJavaScriptEnabled(true)
end
at_exit do
$browser.close
$server.close
end
‘Before’ hooks are run before each scenerio. In first ‘before’ hook server and browser are set up and host is set to “http://localhost:3001” (change it if you want to run app on other address or port). Notice the line: $browser.webclient.setJavaScriptEnabled(false) – it disables javascript by default.
Second Before hook is fired only for scenarios tagged with @js tag. It will be useful for explicitly saying which scenarios should be tested with javascript.
Now it is time to write some scenarios. File features/javascript.feature
Feature: Javascript
In order to test javascript
As a developer
I need a way to run test scenarios with javascript enabled or disabled
@js
Scenario: With javascript
Given I am on the homepage
And I follow "Click me!"
Then I should see "Javascript rocks!"
Scenario: Without javascript
Given I am on the homepage
And I follow "Click me!"
Then I should see "I am also working without javascript!"
Both scenerios rely on “Click me!” link but have different expectations. To run those tests start mongrel (or any other web server):
mongrel_rails start -e cucumber -p 3001 -d
This will fire mongrel in background on port 3001. It’s best to operate on 2 console tabs while using celerity. One of the big drawbacks of using it, is lack of displaying rails exceptions. Because of that I run cucumber on one tab and “tail -f log/cucumber.log” on the other – I can see errors in the log without opening the browser.
Let’s run tests:
rake features
Of course both tests should fail with
Unable to locate Link, using :text and /Click me!/and rails error in log file:
ActionController::RoutingError (No route matches "/" with {:method=>:get}):
Now we can fix it. We need some controller to show the link:
script/generate rspec_controller home
Add
map.root :action => "show", :controller => "home"to routes file. Next copy app/views/layouts/main.html.haml (it just yields action and includes jquery.js and application.js) and jquery.js
You need to also set layout for home controller:
layout 'main'And here comes html and javascript. app/views/home/show.html.haml
= link_to "Click me!", "?clicked=1", :id => "click_me"
#text
- if params[:clicked]
I am also working without javascript!
It displays a link and a text if params[:clicked] is present. So after clicking on that link page will be reloaded with parameter clicked=1 and the text will be displayed.
Let’s check if it’s passing:mongrel_rails restart; rake featuresWe need to restart mongrel before “rake features” in order to load changes because of “cache_classes = true”. It’s one of the drawbacks of using this method, but I’m sure that someone will find out better way to do that.
If you did everything properly the second scenario should pass now. We’re green! :D
Add javascript code to your application in order to make the second scenario pass:
jQuery(function() {
$("#click_me").click(function() {
$("#text").html("Javascript rocks!");
return false;
});
})
Now both scenarios should pass.
To sum up, now you should be able to:- configure rails app with cucumber and celerity
- specify how your tests should be run by placing @js tag on top of javascript scenarios
- test unobtrusive javascript with ease
- figure out better way to reload rails app while testing
- provide better error explanations in cucumber with celurity to test without tailing logs
Cheers!
