In this post we’ll look at using Cucumber to test the experience that users will have with our SimpleDB/Devise integration. Cucumber allows us to specify large grained features, or scenarios, that we want to test and to describe those scenarios in plain language. Then we can implement step definitions that turn our descriptive scenarios into executable functions to excercise the app.
In the ongoing series of using Devise with SimpleDB we’ve looked at
initial integration basic testing and refactoring, styling with Twitter Bootstrap, and
more refactoring. Today we’ll be adding Cucumber to test experience the user has with our integration.
Open the Gemfile
and add this :
group :test do
gem 'cucumber-rails'
gem 'capybara'
gem 'database_cleaner'
end
The run bundle install
Then run rails g cucumber:install --capybara --rspec
Then you need to open features/support/env.rb
and remove or comment out the following lines
begin
DatabaseCleaner.strategy = :transaction
rescue NameError
raise "You need to add database_cleaner to your Gemfile (in the :test group) if you wish to use it."
end
Now you should be able to run cucumber
and see something like this
cucumber
Using the default profile...
Rack::File headers parameter replaces cache_control after Rack 1.5.
0 scenarios
0 steps
0m0.000s
That just means that we don’t have any features that we’re testing yet. Let’s change that.
First, let’s write a very simple scenario that we want to test, namely a successful sign up. Add a new file at features/signup.feature
and add this :
Feature: Sign up As a new user I want to signup with my details So that I can login
Scenario: Sucessful sign up Given a user without an account When he creates a new account Then he should see "Welcome back, trusted user!"
This gives us a very high level description of the scenario. We don’t want to get into too many details here.
When you run cucumber
you should see this:
Feature: Sign up As a new user I want to signup with my details So that I can login
Scenario: Sucessful sign up # features/signup.feature:6 Given a user without an account # features/signup.feature:7 Undefined step: "a user without an account" (Cucumber::Undefined) features/signup.feature:7:in `Given a user without an account' When he creates a new account # features/signup.feature:8 Undefined step: "he creates a new account" (Cucumber::Undefined) features/signup.feature:8:in `When he creates a new account' Then he should see "Welcome back, trusted user!" # features/signup.feature:9 Undefined step: "he should see "Welcome back, trusted user!"" (Cucumber::Undefined) features/signup.feature:9:in `Then he should see "Welcome back, trusted user!"'
1 scenario (1 undefined) 3 steps (3 undefined) 0m3.020s
You can implement step definitions for undefined steps with these snippets:
Given /^a user without an account$/ do pending # express the regexp above with the code you wish you had end
When /^he creates a new account$/ do pending # express the regexp above with the code you wish you had end
Then /^he should see "(.*?)"$/ do |arg1| pending # express the regexp above with the code you wish you had end
Now we’re going to take the step definitions suggested by cucumber and add them to features/step_definitions/signup.rb
Given /^a user without an account$/ do pending # express the regexp above with the code you wish you had end
When /^he creates a new account$/ do pending # express the regexp above with the code you wish you had end
Then /^he should see "(.*?)"$/ do |arg1| pending # express the regexp above with the code you wish you had end
Now when you run cucumber
you should see this:
... Scenario: Sucessful sign up # features/signup.feature:6 Given a user without an account # features/step_definitions/signup.rb:1 TODO (Cucumber::Pending) ./features/step_definitions/signup.rb:2:in `/^a user without an account$/' features/signup.feature:7:in `Given a user without an account' When he creates a new account # features/step_definitions/signup.rb:5 Then he should see "Welcome back, trusted user!" # features/step_definitions/signup.rb:14
1 scenario (1 pending) 3 steps (2 skipped, 1 pending) ...
So now we have our scenario running, but we need to fill in the step definitions. Here’s an updated set of step definitions that will get everything passing:
Given /^a user without an account$/ do # This condition doesn't require any explicit setup end
When /^he creates a new account$/ do visit "/users/sign_up" fill_in('Email address', :with => 'jeremy@octolabs.com') fill_in('Password', :with => 'sometestpassword') fill_in('Password confirmation', :with => 'sometestpassword') click_button('Sign up') end
Then /^he should see "(.*?)"$/ do |text| page.should have_content(text) end
After filling out the step definitions you should be able to run cucumber
and see a passing test:
Feature: Sign up As a new user I want to signup with my details So that I can login
Scenario: Sucessful sign up # features/signup.feature:6 Given a user without an account # features/step_definitions/signup.rb:1 When he creates a new account # features/step_definitions/signup.rb:5 Then he should see "Welcome back, trusted user!" # features/step_definitions/signup.rb:13
1 scenario (1 passed) 3 steps (3 passed)
But if you run it again, you’ll see a failure:
Scenario: Sucessful sign up # features/signup.feature:6 Given a user without an account # features/step_definitions/signup.rb:1 When he creates a new account # features/step_definitions/signup.rb:5 Then he should see "Welcome back, trusted user!" # features/step_definitions/signup.rb:13 expected there to be text "Welcome back, trusted user!" in "Sign in Sign up SimpleDevise A demo of using SimpleDB with Devise Sign up 1 error prohibited this user from being saved: Email has already been taken Sign in" (RSpec::Expectations::ExpectationNotMetError) ./features/step_definitions/signup.rb:14:in `/^he should see "(.*?)"$/' features/signup.feature:9:in `Then he should see "Welcome back, trusted user!"'
Failing Scenarios: cucumber features/signup.feature:6 # Scenario: Sucessful sign up
1 scenario (1 failed) 3 steps (1 failed, 2 passed)
This new failure is due to the removal of DatabaseCleaner, which means that old records are hanging around in SimpleDB from run to run of the feature scenario. This is easy enough to remedy. Just open up features/support/env.rb
and add this:
Before do
User.all.each{|u| u.destroy }
sleep(1) # Allow SimpleDB to propagate
end
Now you should be able to run the scenario several times in a row and it should keep passing.
Now lets add a scenario for the case where the user tries to register with an email address that has already been taken.
Scenario: Email already taken
Given an existing user with email "jeremy@octolabs.com"
Given a user without an account
When he creates a new account
Then he should see "Email has already been taken"
And one new step definition is all we need to add:
Given /^an existing user with email "(.*?)"$/ do |email|
User.create! :email => email, :password => "testpass", :password_confirmation => "testpass"
end
This will get everything working, but it could use a little clean up. The biggest problem is that we’ve hardcoded an email address into the step definition for creating a new user, but then we are explicitly using the same address in a scenario step to create the existing user. Let’s update the When he creates a new account
steps to be
When he creates a new account with email "jeremy@octolabs.com"
Aside from updating the scenario steps, we just need to update the step definition:
When /^he creates a new account with email "(.*?)"$/ do |email|
visit "/users/sign_up"
fill_in('Email address', :with => email)
fill_in('Password', :with => 'sometestpassword')
fill_in('Password confirmation', :with => 'sometestpassword')
click_button('Sign up')
end
That’s it for this post. Now we have Cucumber running a couple of feature scenarios that describe real interactions that we want our users to experience in the app.