Translating BDD Features into Capybara RSpec Tests

At Onfido our engineering team is adopting Behaviour-Driven development (BDD) as our development methodology. One of our biggest challenges in doing so has been integrating BDD into our existing stack, namely Rails, Rspec, and Capybara.

To help ease the process for anyone else going through a similar transition, here’s a practical guide to our process for converting semi-formal product requirements into BDD Features and finally into Capybara Rspec feature tests.

The Feature

One of the products Onfido offers is an employment history verification. An Applicant submits information for a period of employment, including the details of a Referee who can confirm this information. The Referee is sent an automated email with a link to a survey where they can confirm or override any of the information the Applicant has submitted.
With the addition of a few key customers, a need to extend this functionality was identified. Historically, our feature definition process has been to break features down into three elements: problem, solution, and details. Our example feature follows this model:

  • Problem: For reasons of internal regulations, self-service clients need to be able to keep track of what contact has been made with Applicant Referees. Currently our automated emails are completely hidden from them.
  • Solution: The full, long-term solution is to provide an audit trail including all email correspondence related to an Applicant and his employment history. In the short-term it would be sufficient for these outbound Referee emails to include the client’s email in the BCC so that the client can internally keep a track of the correspondence.
  • Details: On the Notification Settings page, add an option for clients to opt into receiving these emails as well as an input for specifying the email address these emails should be sent to. As features go, this is pretty well-defined and simple. The problem/solution/details format is clean and to the point, and most engineers could start work right away. In my opinion, that would be foolish; there are still far too many questions left unanswered.

Translating to a BDD Feature

One of my favourite things about BDD is that it formalizes the informal. It asks us to have conversations (informal), and translate them into Features and Scenarios (formal). Its discipline is rigorous enough to define complicated software, but casual enough to involve non-technical people in the process.

With that in mind, before I wrote my BDD feature, I had a few conversations with the Head of Product and one of our designers. Here are a few of the many questions that came up:
* If the User hasn’t opted in to this feature, should they still see the field for entering a bcc email address?

  • Does the User get BCC’d on all emails sent to Referees, or just in certain situations?
  • Should the BCC email address default to the user’s email address?

Had I jumped right into developing, these questions would’ve certainly come up at some point. It could happen early in development, or it could happen once I was knee-deep in code that might be rendered irrelevant by their answers. This code-wastage risk is what BDD helps us avoid. By formalizing conversations, we’re asked to dig a bit deeper into the details of a feature before we begin development.

After enough data had been gathered, I formalized these conversations into a BDD Feature definition. You may notice that I use Gherkin as a syntactical guideline; I never actually execute it, I simply find it easy to read and follow.

Feature: Client BCC Referee Emails
In order to have visibility into what communications have been sent to Referees As a Self-Service User I want to be able to choose whether any emails sent to Referees should be BCC'd to an email address of my choosing

Scenario: Notifications Settings Page - BCC checkbox Given I have visited the Notifications Settings Page When the page has loaded Then I should see a checkbox with the label 'BCC Employment Referee Emails' And I should not be able to see the 'BCC email' textfield

Scenario: Notifications Settings Page - Show BCC email textfield Given I have visited the Notifications Settings Page When I check the BCC checkbox Then I should see a textfield with the label 'BCC email' appear And the textfield should be pre-populated with my email address

Scenario: Notifications Settings Page - Show BCC email textfield Given I have visited the Notifications Settings Page And I have checked the BCC Checkbox When I uncheck the BCC checkbox Then I should see the 'BCC email' textfield disappear

Scenario: Notifications Settings Page - Saving Given I have visited the Notifications Settings Page And I have selected the BCC checkbox And I have filled the BCC email textfield When I click Save Then the checkbox boolean and email string should be persisted in the database

Scenario: Mailing Given I have enabled Referee BCC When A scheduled email of type [type] is sent Then the email should be BCC'd to my configured BCC email

Examples:
  [type]
  employment_referee_reminder
  employment_gap_referee_reminder

I believe the best attribute of BDD Features is their readability. In just five human-readable scenarios, we’ve defined the feature at a very fine granularity. I can pass the above to anyone on my team for feedback. Iterating based on stakeholder feedback is expected, and since it’s just text, the time spent doing so is trivial.

Note: Some may say the granularity of the above Feature is too fine, and they may be right – toggling visibility with a checkbox is hardly difficult or new. In the end, it’s up to the engineer and their team to decide what granularity is right for them.

Translating to RSpec & Capybara

The next step is to translate the BDD Scenarios into RSpec/Capybara acceptance tests. For the sake of brevity, I’m only going to describe the translation of the first four scenarios. The fifth scenario is much more implementation-specific (ours depends heavily on Rails Mailer), so it’s outside the scope of this post.

Here’s the spec boilerplate:

I’ve used the describe method to define the feature name, configured Capybara to use webkit, and logged in a user created with FactoryGirl. Since all four of my scenarios require the Notifications Settings Page to have loaded, I can nest all of my tests within a context block that loads the page.
Note: context is programmatically an alias for describe, but has a contextual difference. describe is generally used to group tests based on functionality, while context is used to group functionality based on state. Lab Matrix has written a great post about this.
Let’s translate with the first scenario:

Scenario: Notifications Settings Page - BCC checkbox
Given I have visited the Notifications Settings Page When the page has loaded Then I should see a checkbox with the label 'BCC Employment Referee Emails' And I should not be able to see the 'BCC email' textfield Here’s how that looks in RSpec:

context 'when the page has loaded' do
BCCCHECKBOXSELECTOR = '#settingsbccRefereeemails' BCCEMAILSELECTOR = '#settingsbcc_email'

before do visit '/dashboard/settings/notification' end

it 'renders the BCC Checkbox and hides the BCC textfield' do expect(page).to havecontent('BCC Employment Referee Emails') expect(page).to haveselector(BCCCHECKBOXSELECTOR, visible: true) expect(page).to haveselector(BCCEMAIL_SELECTOR, visible: false) end end

This test covers all requirements in the BDD Scenario. RSpec purists may object to testing multiple elements in one example – and I agree with them for unit testing – but feature tests are very slow, so it makes sense to reduce their total when possible.
Note: There’s a little bit of cheating here. The selector constants are determined by ActionView::Helpers::FormHelper, and are implementation specific. If anyone has a better way of using Capybara to determine an element’s visibility, please comment below!
Next, we’ll look at testing the checkbox functionality:

Scenario: Notifications Settings Page - Show BCC email textfield
Given I have visited the Notifications Settings Page And I have checked the BCC Checkbox When I uncheck the BCC checkbox Then I should see the 'BCC email' textfield disappear Here’s how it looks in RSpec:

context 'when the page has loaded' do

# Other specs here

context 'when I check the BCC checkbox' do before do find(BCCCHECKBOXSELECTOR).click end

it 'reveals the BCC email textfield and pre-populates it with my email' do
  expect(page).to have_selector(BCC_EMAIL_SELECTOR, visible: true)
  expect(page).to have_content('BCC Email')
  expect(find(BCC_EMAIL_SELECTOR)['value']).to eq user.email
end

end

context 'when I uncheck the BCC checkbox' do before do find(BCCCHECKBOXSELECTOR).click # Check find(BCCCHECKBOXSELECTOR).click # Uncheck end

it 'hides the BCC email textfield' do
  expect(page).to have_selector(BCC_EMAIL_SELECTOR, visible: false)
end

end end

And finally, let’s look at testing the result of pressing ‘Save’

Scenario: Notifications Settings Page - Saving
Given I have visited the Notifications Settings Page And I have selected the BCC checkbox And I have filled the BCC email textfield When I click Save Then the checkbox boolean and email string should be persisted in the database In RSpec:

context 'when the page has loaded' do

# Other specs here

context 'when I fill the form and save' do let(:bcc_email) { 'bcc@myemail.com' }

before do
  find(BCC_CHECKBOX_SELECTOR).click
  find(BCC_EMAIL_SELECTOR).set bcc_email
  click_button('Save')
end

it 'persists the notification settings' do
  setting = SettingsModel.where(user_id: user.id).first!
  expect(setting.bcc_Referee_emails).to be true
  expect(setting.bcc_email).to eq bcc_email
end

end end

You could also verify persistence by refreshing the page and checking what values are filled in the form, but I personally prefer querying the database directly – it’s a bit faster and more specific.
That’s all of them! Now, when we run the tests, we get a very specific list of feature requirements:

Notification Settings Page
when the Notifications Settings Page has loaded renders the BCC Checkbox when I click the BCC checkbox reveals the BCC email textfield and pre-populates it with my email when I uncheck the BCC checkbox hides the BCC email textfield when I fill the form and save persists the notification settings

What about Given/When/Then?

There are a number of RSpec gems that allow you to write specs in a format more closely linked to BDD scenarios. Personally, I like to avoid layers of indirection whenever possible.

What about “should”?

The use of “should” in the context of Given/When/Then is very powerful, because it subtly pushes us towards thinking about outcome over implementation while having a conversation. “What should it do?” is a much more open-ended question than “What does it do?”. That said, once we get to the RSpec stage, implementation takes priority, and is reflected in the present-tense absolute language of the tests.

Conclusion

I hope that this post has shed a bit of light on the process of translating real-world features into RSpec tests. The next step is to start test-driven development using the Red/Green/Refactor loop. There are a thousand great posts about how to do that, so I won’t speak to that part of the process.
Please comment below if you have any thoughts or questions!

Author image
Blake was a Ruby Engineer par excellence here at Onfido, until he left to go see half the countries on the planet. Hopefully, he'll be back soon, if only to play a few games of pool!
top