Object Oriented Test Design in Cucumber (Part 2) - Test Services
This is part of a series on object oriented integration test design. See the other parts:
Service Objects
There's been a shift in Rails the last couple years away from the fat model, skinny controller paradigm. Instead, there's a lot of momentum behind the Service Object Pattern, which allows for encapsulating complex operations in small, focused plain old ruby objects.
There's a lot of advantages to small objects. Most importantly it is perfectly clear what the class does and they are easy to test as a result.
Test Service Objects
Test Suites develop their own bits of complicated logic over time, and the Service Object Pattern can help us here as well.
Let's say you have a cucumber feature that tries to describe an HTML table with a cucumber one:
Then I should see search results like:
| Username | Email Address |
| DVG | dvg@github.com |
The default step definition would look something like this:
Then(/^I should see search results like$/) do |table|
# table is a Cucumber::AST::Table
end
Comparing a Cucumber Table with an HTML table is not a 1-line thing. It is possible, but it takes several steps.
First of all, Cucumber::Ast::Table implements a #diff! method you can use to compare the html table to the expected table. But you need to convert the HTML Table to a 2D array.
Then(/^I should see search results like$/) do |table|
actual_table = find("#search_results").all("tr").map do |row|
row.all("td, th").map do |cell|
cell.text
end
end
expect { table.diff!(actual_table) }.to be_nil
end
So here we do a double #map
operation to convert the rows and cells into a 2D array of strings with the cell contents. This will work, but it's not reusable as it currently stands. A test service would serve us well here
class TableComparison
attr_accessor :cucumber_table, :selector
def initialize(cucumber_table, selector)
@cucumber_table = cucumber_table
@selector = selector
end
def matches?
cucumber_table.diff!(build_2d_array_from_html_table).nil?
end
private
def build_2d_array_from_html_table
find(selector).all("tr").map do |row|
row.all("td,th"),map { |cell| cell.text }
end
end
end
Now we have an object that can do the heavy lifting for us
Then(/^I should see search results like$/) do |table|
expect { TableComparison.new(table, "#search_results").matches? }.to be_true
end
Combine Test Services with Page Objects to keep your step definitions clean
As a final step, we'll move the validation logic to the page object, and then simply ask the page if it's correct
# features/support/pages/search_results_page.rb
class SearchResultsPage < SitePrism::Page
def has_results_matching?(table)
TableComparison.new(table, "#search_results").matches?
end
end
# featrues/step_definitions/search_results_page_steps.rb
Then(/^I should see search results like$/) do |table|
expect(@app.saerch_results_page).to have_results_matching(table)
end