Mixing Cucumber with Test::Unit/Shoulda

Joe Ferris

I don’t know if cucumber is a fruit or vegetable


There’s a lot of buzz in the community right now about the latest framework for top-down BDD: Cucumber . Cucumber lets you describe features in plain text, from a user’s perspective. Once you have your thoughts down in English (or another language), you can map each step of your spec to Ruby code. This perfectly complements our love of design-driven, outside-in development.

Let’s be assertive

Most of Cucumber’s users seem to be fans of RSpec, but if you’re using Test::Unit (or Shoulda), Cucumber works just as well. First, you’ll have to tell Cucumber not to use RSpec:

# at the bottom of features/support/env.rb
# Comment out the next two lines if you're not using
#RSpec's matchers (should / should_not) in your steps.
# require 'cucumber/rails/rspec'
# require 'webrat/rspec-rails'

Next, you have to replace the matchers in the generated steps to use assertions:

# At the bottom of features/step_definitions/webrat_steps.rb
Then /^I should see "(.*)"$/ do |text|
  # response.body.should =~ /#{text}/m
  assert_match /#{text}/m, @response.body

Then /^I should not see "(.*)"$/ do |text|
  # response.body.should_not =~ /#{text}/m
  assert_no_match /#{text}/m, @response.body

Then /^the "(.*)" checkbox should be checked$/ do |label|
  # field_labeled(label).should be_checked
  assert field_labeled(label).checked?

And that’s it!

Unauthorized cucumbers

Our freshly-released authentication framework, Clearance, also has built-in support for Cucumber. If you’re using Clearance, just run script/generate clearance_features and you’ll get step definitions for authentication, as well as feature files for the generated Clearance controllers for users signing up, in, and out.

Cucumber factories

If you’re using factory_bot in your project, it’s much easier to implement Given steps that create data:

Given /^I have created a post with a title of "(.*)"$/ do |title|
  Factory(:post, :title => title, :author_id => session[:user_id])

You can get a little creative and implement wildcard steps for basic data:

Factory.factories.each do |name, factory|
  Given /^an? #{name} exists with an? (.*) of "([^"]*)"$/ do |attr, value|
    Factory(name, attr.gsub(' ', '_') => value)

That will give you implementation for steps like these, assuming you have post, author, and comment factories:

Given a post exists with a title of "Super Post"
Given an author exists with an email of "user@example.com"
Given a comment exists with a title of "Bad Comment"

If you need to get anymore complicated than that, you should probably write a custom step.

Adding Cucumber to your diet

We wanted to add some integration tests to an existing project, and we decided to use Cucumber. Although that does mean we won’t confidently have 100% coverage in our integration tests, they’re still nice to have. But how do you add features for existing code?

We’ve been writing a feature for every new client request on that project - for each user-created ticket we handle, we create a .feature file (and include the ticket number in the feature title), and write steps for that request. This means that we have acceptance tests for all new client requests on that project. This approach may seem a little strange, but it’s been helpful, and we’re very happy with it so far. We’ll likely take a different approach if we use Cucumber on a project from scratch.

Now you have no excuse if your projects aren’t doing any kind of top-down testing, so get out there and write some acceptance tests!