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.
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:
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.
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:
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.
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.
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.