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.

Installing Cucumber

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.

Writing a feature scenario

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.

Watch it fail

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

Create a step definition file

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

Fill in the step definitions

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

Watch it pass

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)

And another failure…

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)

Keep SimpleDB clean

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.

One more scenario

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.