Capybara with Given/When/Then steps in acceptance testing.
Many of us use cucumber to write acceptance tests.
We must to say it’s very nice tool that allows you to write features in English plain text.
The main goal is provide interface that should be clear for Customer(non-programmer) and Developer.
Cucumber example:
Feature: User signup As a user I want to sign in So I can use service features Background: Given user with "jack@daniles.com" email and "qwerty" password Scenario: Signing in with correct credentials When I go to sign in page And I fill in "email" with "jack@daniles.com" And I fill in "password" with "qwerty" And I click "Login" button Then I should see "Welcome, jack@daniles.com!" Scenario: User tries to sign in with incorrect password When I go to sign in page And I fill in "email" with "jack@daniles.com" And I fill in "password" with "bla" And I click "Login" button Then I should see "Invalid credentials"
It’s very readable. But cucumber has one big downside:
Steps maintenance
All steps are global and a instance variable from one step is accessible in another step.
In big project it can easy lead to mess.
Also you can read some rants about cucumber:
Previously we also use cucumber as tool for user stories.
Lately we found that our customers NEVER read cucumber features.
So do we need it anymore? Let’s check alternatives.
As you probably know, cucumber use internally Capybara for steps implementation. And there is even more – latest version of Capybara comes with a built in DSL for creating descriptive acceptance tests!
So we decided to switch to pure rspec+capybara testing.
We moved our features to spec/acceptance directory.
Let’s look at rspec+capybara example.
RSpec+Capybara example
$ cat spec/acceptance/signup_feature_spec.rb
feature "User signup" do background do @user = Factory(:user, :email => 'jack@daniles.com', :password => 'qwerty') end scenario "Signing in with correct credentials" do page.visit "/sessions/new" page.fill_in "email", :with => "jack@daniles.com" page.fill_in "password", :with => "qwerty" page.click_button "Login" page.should have_content("Welcome, jack@daniles.com!") end scenario "User tries to sign in with incorrect password" do page.visit "/sessions/new" page.fill_in "email", :with => "jack@daniles.com" page.fill_in "password", :with => "bla" page.click_button "Login" page.should have_content("Invalid credentials") end end
Thus approach has one big benefit:
Feature description and feature implementation are located together.
You don’t need anymore to jump between feature and step files to guess where is implementation for a step.
It’s nice but there are also two downsides.
First downsides is that RSpec documentation output will be too poor and it’s not enough informative:
$ rspec -fs -c spec/acceptance/signup_feature_spec.rb User signup Signing in with correct credentials User tries to sign in with incorrect password Finished in 13.55 seconds 2 examples, 0 failures
Second downside is that scenario does not have anymore exact boundary blocks or steps.
It makes harder to understand the scenario flow.
Real user story often has big scenarios and we realized that cucumber steps were cool.
There were two ways:
- switch back to cucumber
- go further and invent
Railswarians never go back :)
Our Solution
If you decided to switch from Cucumber to Capybara acceptance testing try rspec-example steps gem.
With this gem you may use Given/When/Then steps into rspec example!
RSpec+Capybara+Steps example
feature "User signup" do background do @user = Factory(:user, :email => 'jack@daniles.com', :password => 'qwerty') end Steps "Signing in with correct credentials" do When "I go to sign in page" do page.visit "/sessions/new" end And "I fill in right email" do page.fill_in "email", :with => @user.email end And "I fill in right password" do page.fill_in "password", :with => "qwerty" end And "I click login" do page.click_button "Login" end Then "I should see greeting" do page.should have_content("Welcome, #{@user.email}!") end end Steps "User tries to sign in with incorrect password" do When "I go to sign in page" do page.visit "/sessions/new" end And "I fill in right email" do page.fill_in "email", :with => @user.email end And "I fill in wrong password" do page.fill_in "password", :with => "bla" end And "I click login" do page.click_button "Login" end Then "I should see error" do page.should have_content("Invalid credentials") end end end
And documentation output will be:
$ rspec -fs -c spec/acceptance/signup_feature_spec.rb
User signup Signing in with correct credentials When I go to sign in page And I fill in right email And I fill in right password And I click login" do Then I should see greeting User tries to sign in with incorrect password When I go to sign in page And I fill in right email And I fill in wrong password And I click login Then I should see error Finished in 13.55 seconds 2 examples, 0 failures
Now it’s much better. Isn’t it? :)
RSpec Example Steps features
- Does not hack RSpec Example. It only extends it.
- Brings Given/When/Then/And/But steps support into rspec example block that allow you to better organize scenario
- Adds shared_steps DSL
- Adds ability to mark step as pending
- Reports with color about passed/pending/failed step into documentation formating output
Installation
$ gem install rspec-example_steps
Usage
See README for more details.
Just add to spec/spec_helper.rb
require 'rspec/example_steps'
And enjoy Given/When/Then step usage!