Run your specs and features in different environments
By default your specs and cucumber feature both run in the test rails environemnt i.e. RAILS_ENV=test.
However, I need these two environments to be different. First I’ll show you how, then I’ll tell you why.
I want to have my feature run in a cucumber environment i.e. RAILS_ENV=cucumber. Simply edit the features/support/env.rb file and update the default environment value so that you file looks like this:
ENV['RAILS_ENV'] = 'cucumber' Rails.env = 'cucumber'
We still want our feature to hit the test database, so simply create a pointer in your database.yml file to do this:
test: &TEST adapter: ... database: .... host: ... cucumber: <<: *TEST
Lastly, make sure you have a config/environments/cucumber.rb file for your new cucumber environment. I normally just copy the test environment file.
Now when you run your features they will run in their own, separate environment. Groovy.
Optional: You may also want to modify your Gemfile by adding an additional :cucumber group. I’ve never done this and never run into any problems, but bear it in mind.
Why do I need this?
When I run my specs I use spork and autotest. This makes running specs lightning fast. However, you do need to make some tweaks to the test.rb environment file, namely turning class caching off. This is to make sure your specs always run with the latest saved versions of your models.
config.cache_classes = false
For cucumber, you can leave caching on. However, I use database_cleaner for my features with a truncation strategy. This makes the running of selenium features reliable because no transaction has to be rolled back after each scenario – the data is simply truncated.
Simples.
Use FactoryGirl steps in Cucumber features
I came across something VERY useful recently: factory_girl‘s built in cucumber steps. That’s right, factory_girl comes with some built in cucumber steps that can be used in your cucumber features.
Just add the following to your cucumber env.rb file:
require "factory_girl/step_definitions"
Then, assuming you have the actual factory definitions in the first place, you can use all kinds of loveliness, including the use of associations!
For more detailed information, check out Brandon Keepers’ Practical Cucumber series.
Speed up your features with a backdoor login route
In any application that tracks users, I’m guessing that the vast majority of features will require that a user is logged in. This however can add a huge amount of overhead to your features at run time.
To speed things up, you can create a backdoor route to allow your features to bypass the login page and form submission, and simply hit a url to create the session.
First, define the route scoped by a condition – you don’t want this in production mode!
if %w( development test cucumber ).include?(Rails.env.to_s) map.backdoor '/backdoor', :controller => :user_sessions, :action => :backdoor end
Then define the controller action. Bear in mind we are using authlogic. Adjust this to suite your own requirements.
# for cucumber testing only def backdoor @user_session = UserSession.new(User.find_by_email(params[:email])) @user_session.save head 'ok' end
Finally, your cucumber step goes from something like this…
Given /^I authenticate successfully$/ do Given 'I am on the login page' And 'I fill in "Email" with "eddie@hotmail.com"' And 'I fill in "Password" with "secret"' When 'I press "Login"' Then 'I should see "Welcome, Service Account (AD)"' end
…to something like this…
Given /^I am already logged in as a user$/ do visit "/backdoor?email=eddie@hotmail.com" end
We shaved nearly 2 minutes off our 12-minute build!
Thanks to this post for the original idea.
Find your slowest-running cucumber features
Our feature set has 275 scenarios with 1175 steps, and it’s growing every day. In total it takes about 11 minutes to run the full set of features. We wanted identify the 20 slowest scenarios so that we could do some refactoring both in our code and tests to make things run a bit faster.
Add the following code to your features/support/env.rb file. All we do is catch each scenario using the Around function and grab the execution time for each scenario. Then display he results at the end of your build process.
scenario_times = {}
Around() do |scenario, block|
start = Time.now
block.call
scenario_times["#{scenario.feature.file}::#{scenario.name}"] = Time.now - start
end
at_exit do
max_scenarios = scenario_times.size > 20 ? 20 : scenario_times.size
puts "------------- Top #{max_scenarios} slowest scenarios -------------"
sorted_times = scenario_times.sort { |a, b| b[1] <=> a[1] }
sorted_times[0..max_scenarios - 1].each do |key, value|
puts "#{value.round(2)} #{key}"
end
end
This will give you output that looks like this:
------------- Top 20 slowest scenarios ------------- 9.49 features/client_mapping.feature::Map clients with invalid upload file 8.42 features/client_mapping.feature::Map clients with valid upload file 7.35 features/indicators.feature::Show indicators after client upload 4.50 features/manage_clients.feature::Delete client .. .. ..
Hope this helps someone.
Stub your external endpoints using WebMock
On one of my projects we integrate with 2 external data suppliers through their APIs. In today’s development world this is fairly common and quite easy to do in terms of the integration.
However, the tricky part is figuring out how to test this properly. Using RSpec it’s easy to test models by stubbing out the functions which call the external services. However, when we run our cucumber features we can’t simply stub a method call on a model.
Hello WebMock
WebMock allows you to stub out your external endpoints at the HTTP level. In other words, you stub the external service and response and then allow your application to perform the remote request normally. Within the scope of your application this is a real cucumber feature i.e. an integration test.
Here’s how we use it…
Add the following code to your features/support/env.rb file:
require 'webmock/rspec' World(WebMock::API, WebMock::Matchers) WebMock.allow_net_connect!
The final line tells WebMock to allow all requests to proceed to the network as expected. This is important because we are running cucumber features.
Next, in the relevant cucumber step we will specify the behaviour for WebMock.
Given /^the remote service will return a valid response$/ do
stub_request(:any, 'http://localhost:4567').to_return do |request|
body = CGI::unescape(request.body)
code = body.match(/<CODE_NBR>(.*)<\/CODE_NBR>/)
response = IO.read("#{RAILS_ROOT}/test/responses/#{code[1]}.xml")
{:body => response, :status => 200}
end
end
By passing a block to the stub_request method we can make WebMock behave in a more dynamic way, without having to stub out each individual request. In this case, we search the request body for a known tag which contains a unique code. We use that code to return an expected XML response we have saved. That’s it! Our cucumber features call our external services and process the responses as expected.
What did we do before?
We previously had a Sinatra app which would return the same pre-rolled responses used above. The behaviour was essentially the same, but it was complicated and it made our cucumber steps harder to follow and debug. It also meant for each test run we had to kick off and tear down WEBrick processes to run Sinatra properly – ugh! It was very clever but the kind of thing you have to re-learn every time you look at it.
And now…
Our features are now much cleaner, a little faster, but most importantly, readable and maintainable.