Integration tests are used to ensure that all software components work together. In the RoR world, Cucumber is a very popular choice, mainly because it allows the use of Gherkins. This set of grammar rules lets us create tests that can be read in a more natural language, which even non-techy people can read and write — that makes all the business logic easier to understand. Turnip gives us all the advantages of Cucumber, but it's been designed to be high performing in comparison with Cucumber.
Hi there! While working on our current project, we decided to add some integration testing (in the past we used to have only API endpoints, no UI). After doing some research, a colleague found the Turnip gem.
The team had been using Cucumber for a long time now, but we wanted to look for a new approach. Once we saw a thoughtbot post and checked the Turnip repo, we decided to give it a chance and try it out — especially because it’s claimed that Turnip works by loading the Rails server just once and is mounted directly to RSpec, which makes it faster than Cucumber.
Why did we decide to use Turnip?
We use continuous delivery to send new changes to production. That means that every single PR is reviewed by the engineers, making sure that all tests are in the green. But to get this information, the test suite has to run for almost 95 minutes to complete all tests — and that’s a long waiting time! (The worst part was that sometimes the test failed at minute 90... and it was very frustrating.)
We wanted to test the suite to be scalable without sacrificing too much waiting time when running specs.
So this leads to a team discussion. We knew that we needed integration specs, there was no doubt about that. But over time our test suite will grow as the application and company do, so we wanted to test the suite to be scalable without sacrificing too much waiting time when running specs.
We decided to maintain a test suite that:
- allowed us to read the specs easily,
- would be easy for the non-technical team that was already used to reading Cucumber specs,
- let the QA team create integration specs in the future by themselves,
- works with a reliable tool that can be used in a production-level project, and avoids a super high learning curve for the new developers that come in the project.
Those were the initial requirements that lead us to use Gherkin, so the QA team would be able to read and create new integration specs much easier than using plain RSpec and Capybara.
Those requirements then lead us to Turnip.
Those requirements then lead us to Turnip, especially because it allows us to use Gherkin, can be integrated with the current RSpec setup, and especially because it allows loading the Rails server just once instead of twice (once for the unit test and the second for the Cucumber specs as Cucumber Rails do).
How did we use it?
The Turnip repo has a good README file, but we found a problem when loading the modules dynamically, because the README file is hardcoded into them and if you have multiple integration specs or modules to load, then it becomes a mess to try to manage all those hardcoded loaded modules. This is the way we implemented it. (If you have any improvement or more ideas I would love to read it in the comments.)
- Install Turnip
group :test do
gem "turnip"
end
- Load Turnip RSpec into your
.rspec
file adding the following line:
-r turnip/rspec
Note: if you don’t have a .rspec
, create it in your project root folder.
- Create a
turnip_helper.rb
file into you spec folder to load your module steps and make the configuration you need for your integration specsspec/turnip_helper.rb
require 'rails_helper'
require 'turnip/capybara'
def steps_module(file_path)
modules_to_include = clean_step_modules(file_path)
modules_to_include.map { |sub_module| sub_module.camelize }.join('::').constantize
end
def clean_step_modules(module_path)
module_path.split('support/').last.gsub('.rb', '').split('/')
end
RSpec.configure do |config|
Dir[Rails.root.join('spec/support/steps/**/*')].each do |f|
config.include steps_module(f)
end
end
- Create your features into the features folder
spec/features/your_features.feature
Note that in our case we created a folder for each entity inside our application, for example:spec/features/profile/profile.feature
,spec/features/user/user.feature
-
Create your step modules, as you notice into the
turnip_helper.rb
the file we will automatically load all the modules that are located into thespec/support/steps/
folder, so you can create the folders you need to manage them. What I did was create a folder for each entity or related steps. For example, I made aspec/support/steps/commons/utils_steps.rb
that defines all the steps that are shared across multiple pages but don’t belong to any specific entity, andspec/support/steps/profile/profile_steps.rb
is the file I created to write all the steps related to the profile entity and its behavior. Nevertheless, you can use any structure you need and works for you. -
Run your specs
bundle exec rspec spec/
or just your featuresbundle exec rspec spec/features
Final result
In the end, we should end up with something like the following:
spec/features/profile/provider_provide.feature
Feature: When I visit my dashboard as a provider
Background:
Given there is a valid-user
And user is logged in
Scenario: I should see my profile image and a greeting
Then I should see 'Welcome, Elon' text
And I should see the provider profile image
pcpp_marketplace/spec/support/steps/commons/initial_data_steps.rb
module Steps
module Commons
module InitialDataSteps
step 'there is a valid user' do
# Your logic to add users
end
end
end
end
pcpp_marketplace/spec/support/steps/login/login_steps.rb
module Steps
module Login
module LoginSteps
step 'user is logged in' do
# Your logic to log in users
end
end
end
end
pcpp_marketplace/spec/support/steps/profile/provider_profile_steps.rb
module Steps
module Profile
module ProviderProfileSteps
step 'I should see :searched_text text' do |searched_text|
expect(page).to have_content(searched_text)
end
step 'I should see provider profile image' do
expect(page).to have_selector('img.provider-image')
end
end
end
end
As you notice we have placed our steps in different files that are in different folders, that is because the configuration we have in the turnip helper will load all the modules located into the spec/support/steps
folder and their children.
I hope this post has helped you to configure Turnip into your project, or at least it has convinced you to use Turnip — or at least give it a try.
As always, if you have any doubts I would like to read them in the comments.
Thanks for reading!
@OzmarUgarte, Senior Consultant at MagmaLabs