Modern Cucumber and Rails: No More Training Wheels

Jared Carroll ·

Last month, cucumber-rails 1.1 was released. This release removed web_steps.rb, a collection of step definitions for interacting with a web app.

For months, web_steps.rb contained a warning of its negative effects on feature maintenance. Like most developers, I ignored the warning. During a recent upgrade of an existing Rails app, I realized it was now gone. Instead of copying and pasting it from an older app or using the newly created cucumber-rails-training-wheels gem, I decided to accept the challenge and refactor its steps out of the app’s existing features.

After the refactor, the features read much better. They were simpler, less verbose, and felt more maintainable. I also cleaned up my factory_girl usage, which was causing issues similar to web_steps.rb. Here’s a short overview of the main refactorings.

Removing Domain Leakage

The first step in writing a scenario is establishing its context. This usually involves creating some domain objects. For setup, I use factory_girl. factory_girl includes step definitions for calling your app’s custom factories from scenarios.

The following scenario is from a simple CMS. The domain model consists of a single Page class with a page_type attribute. factory_girl factories exist for creating various types of pages e.g., a home page, a category page, a topic page, etc. A hierarchy of pages can be created by associating a page with a parent page. This scenario is for creating a subtopic page.

factory_girl generated the three “And the following <factory-name> exists:” steps from the app’s existing factories. Each of these steps includes a table with the names and values of attributes to be passed to the corresponding factory.

The table in both the category and topic step includes an association. For example, in the topic step factory_girl will set the topic’s parent association to the existing page named “Mind”, or if that page does not exist, to a new page with that name.

There are two problems with this scenario:

  1. Dependent on the domain model. This scenario contains the names of classes, their attributes, and even their associations. This is brittle and could break due to domain changes as minor as renaming a database column.
  2. Includes too much detail. This scenario is about creating a subtopic, the actual test data should not matter. The fact that the name of the topic is “Stress” is distracting and irrelevant. The home and category pages are also unnecessary; only a topic page should be required to create a subtopic page. Factories should handle any required associations. These nonessential elements are known as incidental details.

Let’s refactor to something simpler.

This refactored version replaces the former setup with a single step, also provided by factory_girl.factory_girl generates a “Given a <factory-name> exists” for each of your factories. This simpler step definition excludes the ascii table filled with implementation details, eliminating distracting test data, and making the scenario more readable overall.

Strive to use factory_girl‘s simpler step definitions to create more maintainable scenarios. Avoid writing scenarios with the more verbose table based steps by eliminating incidental details. Don’t hesitate to create custom steps if factory_girl‘s steps are too low level for a scenario.

Thinking Declaratively

web_steps.rb provides a lot of low level step definitions for performing actions on a web page, e.g., clicking a link, filling in form fields, clicking buttons, etc.

Here’s a scenario from the same simple CMS project using various action steps provided by web_steps.rb.

There are two problems with this scenario:

  1. Dependent on the UI. Changes to the UI could result in corresponding changes to this feature. Scenarios written in this step-by-step style are known as imperative scenarios.
  2. Contains too many incidental details. A stakeholder doesn’t care about the test data, they just want to verify that you can create a slide.

Let’s refactor and fix these two issues.

A single custom step has eliminated our UI dependency and removed insignificant data. This scenario is much more readable and as a result, more likely to be read.

The important point is to not be lazy and lean on web_steps.rb. Instead, write in a declarative style by creating custom steps that focus on “what” should happen, not “how” it should happen.

Simpler Verification

web_steps.rb also provides several step definitions for verifying page content and form field values.

The omitted verification step of the previous “Create a slide” scenario uses these steps to verify a success message and form field values:

This scenario implies that after creating a slide you’re shown a success message and redirected to the new slide’s edit page. Again, this creates a dependency on the UI and distracts with its “interesting data”.

Let’s refactor by first moving the success message verification into the “And I create a slide” step. Then we can introduce a new higher-level step to summarize the redirection and expected form field values.

Make the Effort

The removal of web_steps.rb was a controversial move but also a good one. Like most developers, I ignored the warnings in web_steps.rb and my scenarios suffered from unexpected failures and UI dependencies. By eliminating web_steps.rb, you’re forced to think declaratively and write your features at a much-higher level. Take the same approach during setup when using factory_girl. I abused factory_girl‘s step definitions and my features soon became lost in ascii tables.

You will have to write more code, but the improved readability and maintenance make it worth the effort.