Get your project from zero to tested leveraging the power of Cucumber and Factory Girl
Testing can be a daunting task, especially for developers who are new to Rails. It sounds like a great idea, but where do you start? What do you test? What don’t you test? For many developers, the hardest part of testing is jumping in and getting your feet wet. Luckily, Cucumber makes getting started with testing easy, you’ll be testing your code form top to bottom in no time.
What is Cucumber?
Cucumber, in simple terms, is a framework for writing human-readable, meaningful tests that allow you to test the full functionality of your Rails project from database access to business logic to what’s displayed in your views.
From the Cucumber Wiki:
Cucumber lets software development teams describe how software should behave in plain text. The text is written in a business-readable domain-specific language and serves as documentation, automated tests and development-aid – all rolled into one format.
When you write your tests with Cucumber you get full-stack automated tests, documentation and a tool to help you communicate with your product owner.
Who’s the Factory Girl?
Factory Girl is a fixture replacement for Rails that allows you to create meaningful objects for use in your tests.
From the Factory Girl Github Page:
factory_girl is a fixtures replacement with a straightforward definition syntax, support for multiple build strategies (saved instances, unsaved instances, attribute hashes, and stubbed objects), and support for multiple factories for the same class (user, admin_user, and so on), including factory inheritance.
When you use Factory Girl in conjunction with Cucumber you can simplify complex test setup and use real objects from the database, not brittle mocks.
Getting Setup
Getting your project setup with Cucumber and Factory Girl is pretty straightforward. First, you’ll need a few gems installed:
sudo gem install cucumber-rails
sudo gem install webrat
sudo gem install thoughtbot-factory_girl --source=http://gemcutter.org
Note: As of 0.5.0 the Rails specific functionality was extracted from the cucumber gem into the cucumber-rails gem
Cucumber comes pre-packaged with some generators for getting your project ready for testing. From the root of your Rails project, run the generator:
ruby script/generate cucumber --webrat --rspec
You’ll notice that we’re passing a couple of flags to the generator, --webrat specifies that we want to use webrat as our automated browser and --rspec specifies that we want to use rspec as our test framework. If you don’t pass these options Cucumber will guess which options you want based on the gems which you have installed.
After running the generator, you will see some output about the files that were created:
create config/cucumber.yml
create config/environments/cucumber.rb
create script/cucumber
create features/step_definitions
create features/step_definitions/web_steps.rb
create features/support
create features/support/env.rb
create features/support/paths.rb
create lib/tasks
create lib/tasks/cucumber.rake
Here’s a brief description of what some of the important files do:
*config/cucumber.rb*: This is your cucumber environment, similar to production.rb or development.rb. Typically you’ll add cucumber specific config.gem directives and similar “cucumber only” items in here.
*features/step_definitions/web_steps.rb*: These are the webrat steps that you get for free with Cucumber and Webrat.
*features/support/env.rb*: This file has Cucumber specific setup and configuration options. This changes often, so it’s recommended to not alter this file directly, but to create your own custom env.rb (e.g. custom_env.rb).
*features/support/paths.rb*: Cucumber needs to be aware of the custom routes in your application that you plan on using when testing, this is where they are specified.
Cucumber from 10,000ft
Now that you’ve got your project configured to use Cucumber, let’s go over a high level view of the general concept of testing with Cucumber.
Cucumber uses “Features” to describe the functionality of your application. Features, stored in the features directory, are written in a simple human-readable format called Gherkin. The “Steps” in your feature files that describe the functionality of your application are backed up by step files in the features/step_definitions directory. The step files act like translations between the human-readable feature files and the underlying Ruby code of your Rails application.
Typically you will be using Cucumber in conjunction with Webrat to test your application. Webrat acts as the person behind the keyboard clicking around the site, filling out forms and viewing resulting pages. If you’ve ever tested your project by manually filling out forms and verifying output, that’s exactly what Webrat does for you, except it’s automated.
Let’s Get Testing Already
Rather than go into detail about TDD or BDD and why you should use them, I’m going to assume that you’re like most Rails developers looking to learn more about testing. You probably have an application or two out there in the wild, seemingly working just fine. However, you know that you should get some test coverage in there so that you can confidently make changes and debug problems as they come up. With that in mind, I am going to use a sample application that already has some functionality and we will add our Cucumber tests where needed.
A Veterinary Patient Management System
Our sample application is a simple patient management system for a Veterinarian that deals with Owners, Pets and Visitations. Let’s take a look at some of the models.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
class Owner < ActiveRecord::Base
has_many :pets
validates_presence_of :name
validates_presence_of :email
end
class Pet < ActiveRecord::Base
belongs_to :owner
validates_presence_of :name
validates_presence_of :species
validates_inclusion_of :species, :in => %w( dog cat bird snake ),
:message => "Species: %s is not included in the list of accepted species"
end
class Visitation < ActiveRecord::Base
has_one :pet
end |
The first thing for which we’ll be adding a Feature is the process for creating a new Owner record. When a new client calls the Veterinarian’s office, an employee needs to enter them into the system.
Writing a Feature
Cucumber features are very straight forward. The feature file explains a set of functionality by describing different cases through scenarios.
1 2 3 4 5 6 7 8 9 10 |
Feature: Manage Owners
In order to value
As a role
I want feature
Scenario: title
Given context
When event
Then outcome |
Each Cucumber feature represents a desired software feature that a certain user of the system wants in order to achieve some end goal. The scenarios are formatted so that a context is setup, an event occurs and an outcome is evaluated.
Let’s examine our real Manage Owners feature.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
Feature: Managing Owners
In order to manage our client list
As an employee
I want to be able to CRUD owners
Scenario: Creating a new Owner
Given I am on the homepage
And I follow "Owners"
Then I should be on the owners index page
Given I follow "New owner"
And I fill in "Name" with "Clayton"
And I fill in "E-Mail" with "clayton@example.org"
And I fill in "Address" with "100 Cactus Rd"
And I fill in "City" with "Scottsdale"
And I fill in "State" with "Arizona"
And I fill in "Postal Code" with "85000"
And I fill in "Phone" with "480-555-1212"
When I press "Create"
Then I should see "Owner was successfully created."
And I should be on the owners index page |
Note: When you specify “And” in a Cucumber feature the step inherits the Given/Then/When from the previous step
Our scenario covers the act of creating a new Owner record. We first setup a context (I am on the homepage). Next we click a link in the navigation (I follow “Owners”). Once we’re on the index view for the Owners controller (I should be on) we can click another link to get to the new view for the Owners controller. From there we are simply instructing Webrat to fill out our form (I fill in) and click the submit button at the bottom (I press “Create”). Finally we are asking Webrat to read the resulting page and see if there is some text present (I should see).
Our feature is looking pretty good, but we still have a few more steps before we can get this scenario passing. Running the this feature from the command line will give us some output and more direction. Once you’re run rake db:migrate and rake db:test:prepare run the following from your application’s root directory:
ruby cucumber features/manage_owners.feature
Looks like we’re failing, and Cucumber gave us some information about what’s wrong.
Can't find mapping from "the owners index page" to a path. Now, go and add a mapping in patient-management/features/support/paths.rb
If we open up our features/support/paths.rb file we can add the correct path right below the default path for the “home page”. The paths file is really just a list of regular expressions that Cucumber uses to match named routes to words in scenarios.
1 2 3 4 5 |
when /the homes?page/
'/'
when /the owners index page/
owners_path |
With that little change we have our first passing Cucumber scenario! When running Cucumber features from the command line, Cucumber will print out a little summary message.
1 scenario (1 passed)
14 steps (14 passed)
0m0.290s
Complex Scenario Setup
In our above example the setup, or Given steps, were pretty simple. However, sometimes you need more complex setups for your scenarios, like editing a record for example. Cucumber makes it easy to create objects in your scenarios using tables in multiline step arguments.
Here is a feature that goes through the process of editing an owner’s address. You’ll see the Cucumber table that is used to setup our existing owner record. The first row in the Cucumber table correspond to some of the attribute names for our Owner model.
1 2 3 4 5 6 7 8 9 10 11 12 | Scenario: Editing an existing Owner
Given the following owners:
| name | email | address |
| Clayton | clayton@example.org | 100 Cactus Rd |
Given I am on the homepage
And I follow "Owners"
Then I should be on the owners index page
When I follow "Edit"
And I fill in "Address" with "567 N Scottsdale Rd"
When I press "Update"
Then I should see "Owner was successfully updated."
And I should be on the owners index page |
When we try to run this, Cucumber will give us the “shell” for our custom step definition “Given the following owners”.
Given /^the following owners:$/ do |table|
# table is a Cucumber::Ast::Table
pending # express the regexp above with the code you wish you had
end
Since we never created a step definition for this Cucumber feature, we can go ahead and add a new file, features/step_definitions/manage_owners_steps.rb. It is important to note that Cucumber will load all of the files in features/step_definitions and that steps can be used across scenarios and features. If you think you’re going to re-use a step definition, it might be a good idea to place it in something like features/step_definitions/shared_steps.rb.
Note: You should put some thought into your step organization as your project grows in size.
Cucumber takes the table argument from your scenario and turns it into an array of hashes, which is technically a Cucumber::Ast::Table object. By iterating through each hash of the array, we can build up an object that we can use later in our tests. Since we want this object to live in the database and we don’t want to describe every attribute of the owner in our scenario, we can use Factory Girl to simplify the process.
The factory for our owner is simple. I won’t go into the specifics of how to create factories as that’s a whole other article. You can read more about using factories on the github project page
Create a file to store your factories in features/support/factories.rb and copy the following.
1 2 3 4 5 6 7 8 9 10 | require 'factory_girl' Factory.define(:owner) do |o| o.name "John Doe" o.email "john@example.org" o.address "123 Elm Street" o.city "Phoenix" o.state "Arizona" o.zip "85000" end |
When we use the factory to create our object, Factory Girl will take the attribute hash that we’ve passed in when creating the object and use those values for the object. Factory Girl will also fill in any blank attributes with the defaults we’ve specified in the factory definition. The record that is created will actually exist in the database, it is not a mock and it does not have any stubs, as far as our Rails app is concerned it is just a regular record in the database.
1 2 3 4 5 6 7 | Given /^the following owners:$/ do |table| table.hashes.each do |attributes| # {:name => "Clayton", :email => "clayton@example.org", :address => "100 Cactus Rd"} Factory.create(:owner, attributes) end end |
Another place we could use our Given the following owners step would be when creating a feature that deals with creating Pet records. An owner has_many pets while a pet belongs_to an owner. When we create a pet record, we need an existing owner record to which we’ll link the pet record. Start by creating a new feature file, features/manage_pets.feature.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | Scenario: Creating a pet
Given the following owners:
| name | email |
| Clayton | clayton@example.org |
Given I am on the homepage
And I follow "Pets"
And I follow "New pet"
When I fill in "Name" with "Bruno"
And I select "Dog" from "Species"
And I select "March 19th, 2005" as the "DOB" date
And I select "Clayton (clayton@example.org)" from "Owner"
When I press "Create"
Then I should see "Bruno was successfully created."
And I should be on the pets index page
And a pet named "Bruno" should be owned by an owner named "Clayton" |
In this example we have re-used our Given the following owners table. We have also made use of some new Webrat steps like I select for interacting with select lists and I select [DATE] as the [DATE LABEL] date for selecting a date from Rails’ date_selector helper. Finally, there is a custom step that we are going to use to make sure that the connection between our newly created pet and the existing owner is in place.
Let’s take a look at the step definition for our custom step above.
1 2 3 4 |
Then /^a pet named "([^"]*)" should be owned by an owner named "([^"]*)"$/ do |pet_name, owner_name|
Pet.find_by_name(pet_name).owner.name.should == owner_name
end |
When you place something in double quotes in your scenario steps, like “Bruno” and “Clayton” in our example, they are captured using regular expressions in the step definition. Cucumber then passes the matched values along so that then can be used in your assertion. We can find the pet based on the pet_name and make sure that the owner linked to this pet via the belongs_to association is the same as the owner_name we specified in our scenario. This is an example of how Cucumber can be used, with RSpec matchers, to make an assertion that has nothing to do with inspecting the DOM of a resulting webpage.
Note: Beware of the Conjunction Steps Anti-pattern
Scenario Outlines
Cucumber provides a very simple way to test multiple different situations with a single scenario. These might be edge cases or just repetitive examples that don’t require their own scenario.
We have some business logic in our application that determines when an appointment, or Visitation, can be made with the Veterinarian:
- Visitations less than three days from today require approval before being booked
- No visitations can be booked more than six months in advance
- No visitations can be booked in the past
We can easily represent this using a Cucumber Scenario Outline. Start by creating a new feature features/visitation_logic.feature. Also, because we’re going to be working with time and date specific business logic, this is a good time to point out that we can stub methods when using Cucumber, however, this practice is frowned upon and should only be used for things like dates and connecting to external APIs.
To add stubbing support add the following to your features/support/custom_env.rb file.
require 'spec/stubs/cucumber'
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
Scenario Outline: visitations
Given the following owners:
| name |
| Clayton |
And the following pets:
| name |
| Bruno |
Given today is "<today>"
Given I am on the home page
And I follow "Visitations"
And I follow "New visitation"
And I select "Bruno (Owned by Clayton)" from "Pet"
And I select "<date>" as the "Appointment Date" date
When I press "Create"
Then I should see "<message>"
Examples:
| today | date | message |
| 2010-01-01 | January 2nd, 2010 | Visitation requires short notice approval. |
| 2010-01-01 | January 31st, 2010 | Visitation succesfully created. |
| 2010-01-01 | November 2nd, 2010 | Visitations cannot be booked that far in advance. |
| 2010-04-01 | January 2nd, 2010 | Visitations cannot be booked in the past. | |
Cucumber will go through the Scenario Outline you have created and substitute the values in <>’s with their corresponding value in the Examples table at the bottom. Cucumber goes through each line in the Examples table and runs the scenario using your specified values.
First, lets implement the step for stubbing our today’s date.
1 2 3 4 |
Given /^today is "([^"]*)"$/ do |date|
Date.stub(:today).and_return(Date.parse(date))
end |
In features/step_definitions/shared_steps.rb we can add the implementation for Given the following pets. We are cheating here because we’re not explicitly linking the Pet to the Owner in our scenario outline. This example isn’t so bad, but keep in mind that this association is not obvious when reading the scenario. This type of “behind the scenes” scenario setup is generally a bad idea as it obscures what’s really happening. Since we’ve already created the owner we can just find the first one in the database and create the pet records using the appropriate Rails’ association method.
Given /^the following pets:$/ do |table|
table.hashes.each do |attributes|
Owner.first.pets.create(attributes)
end
end
The other steps in our scenario already exist, either because we created them, or because they come for free with the built in steps. In this one scenario we were able to test four different business logic outcomes. This test could be expanded to go beyond what is shown on the resulting page, perhaps to ensure that an Approval record was created for the short-notice appointment.
Advanced Cucumber Goodies
While you should now have an understanding of the basics of Cucumber, there are a number of other powerful features.
Tags
Cucumber allows you to “tag” your scenarios and features so that they can be run, not run or have special other tasks run before and after them. Out of the box you get the @wip tag (Work In Progress) which isn’t run as part of the normal rake cucumber:ok process.
Hooks
Using tags, you can run Before and After tasks that run some code before or after a given feature or scenario. You can even run these hooks before or after all of your features. You might need to do some cleanup of generated files in an After hook or some CPU expensive operation before a single scenario that you don’t want running before all of your scenarios.
Table Transformations
The tables that we have been creating use the exact model attribute names for their header rows. This is convenient for the developer, but makes the scenarios harder to understand. This is especially true if you have very strange or legacy attribute names like record_quote_ext_type__c. Using table transformations you can use “Record Extension Type” in your scenario table and map that to record_quote_ext_type__c in your step definition.
Calling Steps from Steps
It’s possible to take a multi-step process, expressed in Cucumber steps, and call them all at once from another step. You might have a few steps that describe logging in to a system (filling out login credentials, pressing a button etc.) You can easily put them all into one step, Given I am logged in, and then reference that elsewhere. This helps to reduce unnecessary repetition of steps and keep your scenarios to a manageable size.
Profiles
Cucumber allows you to specify a profile to use when running your features. By default Cucumber will not run any @wip tagged scenarios and the output will contain any failing files and the line number of the failure. However, if you wanted to run the @production tagged stories and output a nice HTML report, you could setup a new profile for that in config/cucumber.yml and use it when running your features.
Testing Javascript
You can even use Cucumber to test complex javascript. Tools like Celerity with Culerity or Selenium give you the ability go beyond the interactions provided by Webrat. A new addition to Cucumber is Capybara support which aims to unify the language used in steps so that you can use Webrat, Selenium and Celerity side-by-side.
excellent post!
Clayton, I found this post to be fantastic and i’m not sure why it doesn’t have more comments as it’s VERY helpful.
I’ve posted it on rubyflow.com and gave you credit — hopefully more eyes see it because IMO (as a noob) cucumber makes a lot of sense when reading the tutorials and stuff out there … but when you sit down to actually use it some things do not click from the get go like simple “crud” tests….
anyways, thanks dood.
Coming to this article a little late but it is exactly what I needed to push me forward. Thanks for this!
Hi Clayton,
I found this tutorial very helpful.It nice post and really fulfil what I need .let me introduce myself, I am a php developer and new to the Testing and ROR framework .
I am facing one issue during installation process of sudo gem install thoughtbot-factory_girl –source=http://gemcutter.org .
It show me one error: ” ERROR: Could not find a valid gem ‘thoughtbot-factory_girl’ (>= 0) in any repository
ERROR: Possible alternatives: freegenie-factory_girl, malvestuto_factory_girl, jeffrafter-factory_girl, dm-factory_girl, verbose_factory_girl –
“.
So, Please suggest me which possible alternatives is used for better meant of testing with your post.I am unable to predict good choice for testing framework.So Please help me with your valuable suggestion.
Thanks a lot for you suggestion in advance.
Vijay: if you’re using rails you should probably just put this in your Gemfile.
gem “factory_girl_rails”
And then run bundle install
In order to write proper tests
As a developer
I should not us “and” to much
Instead of :
When I fill in “Name” with “Bruno”
And I select “Dog” from “Species”
And I select “March 19th, 2005″ as the “DOB” date
And I select “Clayton (clayton@example.org)” from “Owner”
When I press “Create”
it’s better to say:
When I submit the create form
and specify the exact values for the form fields and action to submit in the step definition. This way the behavior stays the same even if the form changes, and there is a clearer difference between “When I submit the create form” and “When I submit the create form with wrong values”.
For low level tests on what is acceptable input and what is not, don’t use Cucumber.
Also, specify a test per aspect. So, instead of:
Scenario: Creating a new Owner
Given I am on the homepage
And I follow “Owners”
Then I should be on the owners index page
Given I follow “New owner”
And I fill in “Name” with “Clayton”
Do something like:
Scenario: Viewing owners
Given I am on the homepage
And I follow “Owners”
Then I should be on the owners index page
Scenario: Adding owners
Given I am on the “New owner” page (probably without “)
And I fill in “Name” with “Clayton”
Sorry for the spam.
Jasper,
Thanks for the comments. I’ve since change the way I use Cucumber and have come to terms with my previous abuse of the tool. I definitely subscribe to the “You’re cuking it wrong” stuff that’s been going around as well as supporting the recent decision to remove
web_steps.rbfrom cucumber-rails.Unfortunately I haven’t found the time to go back and edit all of my old Cucumber related posts, so they’ll have to stand as a testament to how I used to do things incorrectly.
Awesome post made my day.
Am now Zero To Tested With Cucumber and Factory Girl application owner