It is good programming practice to Don't Repeat Yourself Show archive.org snapshot (or DRY). In Ruby on Rails we keep our code DRY by sharing behavior by using inheritance, modules, traits or partials.
When you reuse behavior you want to reuse tests as well. You are probably already reusing examples in unit tests. Unfortunately it is much harder to reuse code when writing integration tests with Cucumber, where you need to express yourself with Gherkin and step definitions instead of Ruby classes and methods.
But don't dispair! Below you will find many different ways to share code between Cucumber scenarios, allowing you to keep your integration tests as DRY as your application code.
Option 1: Call other step definitions
This is Cucumbers default way of sharing short setup steps or assertions. You can even call step definitions from other step definitions by calling steps
:
When /^I search for "(.+?)"$/ do |query|
steps %{
When I go to the search form
And I fill in "Query" with "#{query}"
And I press "Search"
}
end
Calling other step definitions with steps
has two major limitations:
-
The example above calls other step definitions by piecing together strings. This is a cumbersome way of talking to other code, especially if you are calling step definitions with parameters. You would prefer to use vanilla Ruby methods instead. You can do this by Capybara or by using a test harness (see below).
-
When calling
steps
with multiple lines of Cucumber you lose meaningful stack traces. If a step fails you will always get the same file and line number: The one where you callsteps
. This is a big deal in practice. To remedy this, check out ourmany_steps
helper (see below).
Option 2: Use our many_steps helper
many_steps
is a drop-in replacement for Cucumber's steps
helper. It does everything that steps
does, but gives you meaningful stack traces in case something goes wrong.
To use this helper, copy the attached file to features/support
. Now you can simply call many_steps
instead of steps
:
When /^I search for "(.+?)"$/ do |query|
many_steps(<<-GHERKIN)
When I go to the search form
And I fill in "Query" with "#{query}"
And I press "Search"
GHERKIN
end
If a line is undefined or fails, the stack trace will point to the correct file and line number.
Note that the example above uses Ruby HEREDOC to enter the steps. Actually you can hand any string to many_steps
, but using a HEREDOC section named GHERKIN
gives you Cucumber syntax highlighting in RubyMine.
Option 3: Use a test harness
A test harness is a Ruby module that you include in the Cucumber world. This way the module's methods become available to all step definitions. Think of it as enhancing your Capybara API with app-specific helper methods from your application domain.
Calling methods from a test harness is usually much more convenient than calling other step definitions. Because you are using plain ruby, you can use return values, structured arguments (e.g. hash options), etc.
I often have files like the session_steps.rb
(below) that first define a test harness and then multiple step definitions. The step definitions are a simple wrapper that translate Cucumber regexps to calls of the harness:
module SessionStepsHarness
def current_user_name
element = page.first('.current_user')
element && element.text
end
def signed_in?
current_user_name.present?
end
def sign_in(user, password = 'secret')
visit new_session_path
fill_in 'E-mail', :with => user.email
fill_in 'Password', :with => password
click_button 'Sign in'
end
def sign_out
click_link 'Sign out'
end
end
World(SessionStepsHarness)
Then /^I should be signed in$/ do
should be_signed_in
end
Then /^I should be signed in as "([^\"]+)"$/ do |identity|
current_user_name.should == identity
end
Then /^I should be signed out$/ do
should_not be_signed_in
end
When /^I sign out$/ do
sign_out
end
When /^I am signed out$/ do
sign_out if signed_in?
end
When /^I sign in with "([^\"]+)\/([^\"]+)"$/ do |email, password|
user = User.where(:email => email).find_or_create
sign_in(user, password)
end
Option 4: Use scenario outlines
Scenario outlines Show archive.org snapshot are a way to run the same Cucumber scenario multiple times, but use different placeholder values for each iteration:
Scenario Outline: Only some roles have access to the address book
When I sign in as a <role>
And I go to the address book
Then I should be <access> access
Examples:
| role | access |
| admin | granted |
| sales | granted |
| typist | denied |
| staff | denied |
While scenario outlines can be a good fit, their expressiveness is extremely limited. E.g. you cannot express conditions in an outline, you must make do with placeholder variables in an otherwise static scenario script.
Also when your examples have too many variables, your scenario can devolve into something like this:
Given <records>
And I <setup>
Then <observation>
Now you have simply pushed all the complexity into the Examples
block and you are no longer reusing meaningful amounts of code. It's also a bad way to use Cucumber. Consider using a test harness or the many_steps
helper instead.