October 25, 2009 by alex
On last week’s RailsCamp UK I started hacking on a new CouchApp called HejHej. Its purpose is to help me learn Swedish, but what’s more important here: I wrote this app BDD style using Cucumber, the famous BDD tool and Culerity, my humble addition that allows me to test any webapp (including client side JavaScript) with it.
The whole thing is available on Github so you can check out the features and steps there. In the following post I will show the necessary steps to test your own CouchApps with Cucumber.
First of all you will need to install some software:
JRUBY_HOME/bin/jruby -S gem install culerity --source=http://gemcutter.org
jruby -S gem install cucumber
jruby -S gem install rest-client jruby-openssl
Next change to your CouchApp’s directory and create the following directory structure:
CouchApp root
|- features
|- support
|- env.rb
|- paths.rb
|- step_definitions
|- common_culerity_steps.rb
Copy and paste the following into your env.rb:
require 'rubygems'
require 'culerity'
require 'cucumber/formatter/unicode'
require 'restclient'
Before do
RestClient.delete "#{host}/#{database}" rescue nil
RestClient.put "#{host}/#{database}", ""
system "couchapp push"
end
Warning: the before hook you see there will delete and recreate your CouchDB database before every run, so make sure you are using a separate database for testing than for your actual “production” database.
This is what your common_culerity_steps.rb should look like:
require 'culerity'
Symbol.class_eval do
def to_proc
Proc.new{|object| object.send(self)}
end
end unless :symbol.respond_to?(:to_proc)
Before do
$server ||= Culerity::run_server
$browser = Culerity::RemoteBrowserProxy.new $server, {:browser => :firefox, :javascript_exceptions => true, :resynchronize => true, :status_code_exceptions => true}
$browser.log_level = :warning
end
def host
'http://localhost:5984'
end
def database
'hejhej'
end
at_exit do
$browser.exit if $browser
$server.close if $server
end
When /I press "(.*)"/ do |button|
button = [$browser.button(:text, button), $browser.button(:id, button)].find(&:exist?)
button.click
When 'I wait for the AJAX call to finish'
end
When /I click "(.*)"/ do |link|
When "I follow \"#{link}\""
end
When /I follow "(.*)"/ do |link|
_link = [[:text, /^#{Regexp.escape(link)}$/], [:id, link], [:title, link]].map{|args| $browser.link(*args)}.find{|__link| __link.exist?}
raise "link \"#{link}\" not found" unless _link
_link.click
When 'I wait for the AJAX call to finish'
end
When /I follow \/(.*)\// do |link|
$browser.link(:text, /#{link}/).click
When 'I wait for the AJAX call to finish'
end
When /I fill in "(.*)" with "(.*)"/ do |field, value|
find_by_label_or_id(:text_field, field).set value
end
When /I attach "(.*)" to "(.*)"/ do |value, field|
$browser.file_field(:id, find_label(field).for).set(value)
end
When /I check "(.*)"/ do |field|
find_by_label_or_id(:check_box, field).set true
end
def find_by_label_or_id(element, field)
begin
$browser.send(element, :id, find_label(/#{field}/).for)
rescue #Celerity::Exception::UnknownObjectException
$browser.send element, :id, field
end
end
When /^I uncheck "(.*)"$/ do |field|
$browser.check_box(:id, find_label(field).for).set(false)
end
When /^I select "([^"]+)" from "([^"]+)"$/ do |value, field|
find_by_label_or_id(:select_list, field).select value
end
When /^I select "([^"]+)"$/ do |value|
$browser.option(:text => value).select
end
When /I choose "(.*)"/ do |field|
$browser.radio(:id, find_label(field).for).set(true)
end
When /I go to the (.+)/ do |path|
$browser.goto host + path_to(path)
end
When /I wait for the AJAX call to finish/ do
sleep 0.4
end
When /^I visit "([^"]+)"$/ do |url|
$browser.goto host + url
end
Then /^I should see "(.*)"$/ do |text|
Then "I should see /#{Regexp.escape(text)}/"
end
Then /^I should see \/(.*)\/$/ do |text|
# if we simply check for the browser.html content
# we don't find content that has been added dynamically,
# e.g. after an ajax call
div = $browser.div(:text, /#{text}/)
begin
div.html
rescue
#puts $browser.html
raise("div with '#{text}' not found")
end
end
Then /I should see the text "(.*)"/ do |text|
$browser.html.should include(text)
end
Then /I should not see the text "(.*)"/ do |text|
$browser.html.should_not include(text)
end
Then /I should not see "(.*)"/ do |text|
div = $browser.div(:text, /#{text}/).html rescue nil
div.should be_nil
end
Then /I should see no link "([^"]+)"/ do |text|
$browser.link(:text => text).should_not exist
end
Then /I should not find the page "([^"]+)"/ do |url|
no_exception = false
begin
$browser.goto host + url
no_exception = true
rescue => e
e.message.should =~ /404/
end
no_exception.should be_false
end
Then /^"([^\"]*)" should be chosen$/ do |field|
find_by_label_or_id(:radio, field).should be_checked
end
Then /^"([^\"]*)" should be checked$/ do |field|
find_by_label_or_id(:check_box, field).should be_checked
end
Then /^"([^\"]*)" should not be checked$/ do |field|
find_by_label_or_id(:check_box, field).should_not be_checked
end
Then /^"([^\"]*)" should be selected$/ do |selection|
$browser.option(:text => selection).should be_selected
end
When 'show response' do
p $browser.url
open_response_in_browser
end
def find_label(text)
$browser.label :text, text
end
def open_response_in_browser
tmp_file = '/tmp/culerity_results.html'
FileUtils.rm_f tmp_file
File.open(tmp_file, 'w') do |f|
f < < $browser.div(:id, 'content').html
end
`open #{tmp_file}`
end
</code>
Make sure to change the database name (and host if necessary).
Finally the paths.rb
module NavigationHelpers
def path_to(page_name)
case page_name
when /start page/
"/#{database}/_design/#{database}/index.html"
else
raise "Can't find mapping from \"#{page_name}\" to a path."
end
end
end
World(NavigationHelpers)
Now that you are done with the prerequisites you can write your first feature. Create a file start_page.feature
in the features
directory and paste the following:
Feature: start page
In order to feel welcome
As a user
I want to be welcomed on the start page
Scenario: go to the start page
When I go to the start page
Then I should see "Welcome"
Now you can run your first feature:
jruby -S cucumber features/start_page.feature
Congratulations, you can now test drive your application. Explore the step definitions in the common_culerity_steps.rb
in order to learn how to traverse links (When I follow “link”), fill out forms etc.
In order to make the feature pass you should add the text “Welcome” to the index.html of your CouchApp.