Cucumber is different. That's why you're either going to love it or hate it.
It's useful, no doubt, and I even found myself smiling at the code while I researched and played around for this post. Be that as it may, I think the inherent shock value Cucumber brings with itself won't allow you to be indifferent to it.
It came out of RSpec's story runner - seems like a lot of tools go back to RSpec - to suppress the need of writing stories in Ruby. I don't know what you think, but you could do a lot worse than Ruby!
Nevertheless, Cucumber wants you to use natural language to describe your tests. Imagine that for a moment: your grandmother, who I'm pretty confident doesn't know anything about coding (mine sure doesn't - sorry grandma!), would be able to read & understand what your tests are supposed to do. At the very least, that's inspiring.
On to the good stuff. Cucumber is the de facto standard for acceptance testing in Ruby, with a BDD fashion consultant. Since we're all about Sinatra nowadays, we'll also need some tools for browser testing.
Webrat
(Capybara further down)
Skipping introductions, Webrat was one of the fastest ways to get you started. I say was due to the fact that you probably won't see a new commit or get any kind of support for it since it hasn't been updated in YEARS, but alas, here i am still talking about it - secretly hoping someone will pick up the project so these words don't turn out to be completely useless...
...screw it, it's for educational purposes!
Start a new project and place the simplest server file you can think of inside it, like my test.rb
over here.
require 'sinatra'
get '/' do
"Hello, world!"
end
Next, dive into the Bundler universe and add a Gemfile
.
source 'https://rubygems.org'
gem 'sinatra'
gem 'cucumber'
gem 'rspec'
gem 'webrat'
gem 'rack'
gem 'rack-test'
gem 'guard'
gem 'guard-cucumber'
gem 'foreman'
As a side note, I've also included Foreman so later I can have my Sinatra web server and Guard running in one go.
Why Guard? Like I've being doing on all my Sinatra Tests posts, I've included Guard (and guard-cucumber for this one) so you can let go of your worries when constantly trying to remember to run your tests, specially at the beginning of the development stage of your process. They will run automatically after you save files! To achieve this, first do
$ bundle install
followed by
$ guard init cucumber
You can check more options and other configurations on their repo, which I'm avoiding here for the purpose of simplicity. A Guardfile
should have been generated containing the following relevant lines of code.
guard "cucumber" do
watch(%r{^features/.+\.feature$})
watch(%r{^features/support/.+$}) { "features" }
watch(%r{^features/step_definitions/(.+)_steps\.rb$}) do |m|
Dir[File.join("**/#{m[1]}.feature")][0] || "features"
end
end
These lines will run your features (= tests, fancy wording - I told you there was a fashion consultant here somewhere) every time you save any file in your features folder, be it a support file, your step's definitions or even a feature file.
Wait, what?
Cucumber comes with a certain folder structure - it's an opinionated set of tools - it assumes you organize your features, step definitions, and supporting code in a particular fashion. You can override its defaults of course, but life will be so much brighter easier if you don’t. The standard directory structure is as follows:
features
├── step_definitions
└── support
└── env.rb (commonly used, not mandatory)
- Inside your features folder will be you tests per se, files with a
.feature
extension which can be organized into subdirectories. - The step_definitions folder contains, quite literally, the step definitions of your features in
.rb
files. Each feature is a collection of steps, which will execute these lines of code. - Lastly, a support folder contains Ruby code which will be loaded before your step_definitions, useful for environment settings or any other operation you need to run before you start testing.
Got it? Good! Now type in
$ cucumber --init
And check it provides some output like
First things first, let's set up our environment with initializers code and generic configurations! Or cheat and get a readily available file from the Cucumber Wiki under 'Webrat'.
# NOTE: change the filename on this line to match yours!
require File.join(File.dirname(__FILE__), *%w[.. .. test.rb])
# NOTE: I deleted lines related to 'app_file'
require 'rspec/expectations'
require 'rack/test'
require 'webrat'
Webrat.configure do |config|
config.mode = :rack
end
class MyWorld
include Rack::Test::Methods
include Webrat::Methods
include Webrat::Matchers
Webrat::Methods.delegate_to_session :response_code, :response_body
def app
Sinatra::Application
end
end
World{MyWorld.new}
There's a warning note in the wiki under this example to set the Sinatra app_file, and I saw that reference in other articles too. However, I believe this is no longer necessary as I didn't experience any errors or shortcomings when running the project without those concerns.
Next, let's create the first feature as features/test.feature
Feature: view pages
Scenario: Homepage
Given I am viewing "/"
Then I should see "Hello, world!"
Isn't that something?
Well, not really, nothing's running yet so get to the chopper (cheesy!) command line and type in
$ cucumber features
# (or simply cucumber)
Here is what you should see:
Basically, Cucumber is saying it recognizes your code, it sees your feature, yet it doesn't know what to do to complete it. That's where steps come in. As you can see in your terminal, Cucumber will be kind enough to provide you with snippets for what's missing. At this point, create a step_definitions/test_steps.rb
file, copy the snippets and adapt them to run what's necessary to complete the tests.
Given(/^I am viewing "([^"]*)"$/) do |url|
# Webrat method
visit(url)
end
Then(/^I should see "([^"]*)"$/) do |text|
# RSpec expectations
expect(response_body).to match(Regexp.new(Regexp.escape(text)))
end
As you can see, nothing too fancy was added (we fired the consultant for political reasons). We simply go to the provided URL with the first step and check what the response body was with the second one. Rerun cucumber features
and voilá!
I'll add a nifty extra here for your benefit: no one will be interested in reviewing a large stack of features on a command line. Fortunately, Cucumber provides us with a --format
option which in turn can export the results to a well-suited, well-dressed, well-styled HTML file - we add to re-hire the consultant to avoid being sued...
To avoid having to write the command every time I wanted a new & flamboyant freshly baked HTML file, I added it to a rake task instead. There's more on using Rake with Cucumber. Create a Rakefile
with the following lines:
require 'rubygems'
require 'cucumber'
require 'cucumber/rake/task'
Cucumber::Rake::Task.new(:features) do |t|
# t.cucumber_opts = "features --format pretty"
t.cucumber_opts = "features --format html > features.html"
end
To create an HTML file with the latest fashion trends, effortlessly run
$ rake features
A features.html
should be created in your project folder. Opening it with your favorite web browser, you'll see something like this.
Finally, to have your server running along with Guard, create a Procfile
...
web: bundle exec ruby test.rb
guard: bundle exec guard -i
...run...
$ foreman start
...and that's it! Now whenever you save any file on your features
folder, Cucumber will run them automatically. It may also be handy to add a line on your Guardfile
where your features will run every time you save files inside a lib folder for instance.
Notwithstanding, the fact is Webrat is outdated and may not be everyone's cup of tea these days.
Capybara
Capybara on the other hand is what the cool kids are using nowadays. This large rodent toolbox will help you throughout your whole testing phase, is actively maintained (at least until they come up with something newer & better), and you can go back on these posts and use it with RSpec and MiniTest::Spec!
Waste no more time, create a new project folder and load up a new Gemfile
source 'https://rubygems.org'
gem 'sinatra'
gem 'cucumber'
gem 'cucumber-sinatra'
gem 'rspec'
gem 'capybara'
gem 'rack'
gem 'rack-test'
gem 'guard'
gem 'guard-cucumber'
gem 'foreman'
After running the aforementioned $ bundle install
and $ guard init cucumber
, I hope you'll notice an unusual gem up there cucumber-sinatra. Straight out of their description, it reads:
This little gem will help you to initialize a cucumber environment for a sinatra application. It will generate the required files from templates.
Simple right? Take a moment to analyze your life~~'s fashion choices~~. When you're done, I want you to run
$ cucumber-sinatra init --app Test test.rb
[ADDED] features/support/env.rb
[ADDED] features/support/paths.rb
[ADDED] features/step_definitions/web_steps.rb
[ADDED] test.rb
[ADDED] config.ru
Look at that! There's a bunch of code already added for you. Automatically! What the hell!? And I made you go through all that setup manually with Webrat. You're welcome!
If you followed the previous chapter, this setup should be pretty straightforward now, as well as the most of the code in it since test.rb
, env.rb
& web_steps.rb
are easily recognizable and comparable.
To be on the safe side, let me clear the waters: config.ru
is a convention that has become common to deploy Rack-based applications - Heroku has more on it - so you should be aware of its use at this stage. Also, paths.rb
has a NavigationHelpers
module that will map you page names to their actual routes in the application. That's gonna come in handy in a minute.
After analyzing the generated code, it's not too hard to create your first feature.
Feature: view pages
Scenario: Homepage
Given I am on "the homepage"
Then I should see "Hello Test!"
All set! Run your features with $ cucumber
and rejoice.
Well that was easy...and surprisingly anticlimactic for the end of this blog post. Let's keep going!
Since we're testing a web app, I'm going to show you how to interact with a basic interface with these tools. A simple form where you introduce a number and it gets doubled for instance.
First of all, build your web page. Nothing flattering since I don't want to hear anymore about that annoying consultant. Create a views
folder & a double.erb
file in it.
<html>
<head>
<title>Double</title>
</head>
<body>
<form method='post' action='/double'>
<input name='number'/>
<input type='submit' value='Double!'/>
</form>
</body>
</html>
Next, update your routes in test.rb
to include the actions for this new page.
require 'sinatra/base'
class Test < Sinatra::Base
get '/' do
'Hello Test!'
end
get '/double' do
erb :double
end
post '/double' do
"Result: #{params[:number].to_f * 2}"
end
# start the server if ruby file executed directly
run! if app_file == $0
end
Remember what I said about paths.rb
? What do you mean 'no'? Come on, I thought you were reading carefully!
Like I mentioned before (grumbling), there's a helper method that will map your page name to the actual route you'll be using in test.rb. Obviously, we need to add an entry for the new page.
# Taken from the cucumber-rails project.
module NavigationHelpers
# Maps a name to a path. Used by the
#
# When /^I go to (.+)$/ do |page_name|
#
# step definition in web_steps.rb
#
def path_to(page_name)
case page_name
when /the home\s?page/
'/'
# NOTE: Add your route here!
when /the double page/
'/double'
# Add more mappings here.
# Here is an example that pulls values out of the Regexp:
#
# when /^(.*)'s profile page$/i
# user_profile_path(User.find_by_login($1))
else
raise "Can't find mapping from \"#{page_name}\" to a path.\n" +
"Now, go and add a mapping in #{__FILE__}"
end
end
end
World(NavigationHelpers)
All we have to do now is create a feature for it! But wait, that sounds tricky...Nonsense!
One of the perks of using cucumber-sinatra is that the most commonly used steps are already created for you. You only have to know which ones to use. Check web_steps.rb
- so many! - and relax 'cause we will only be using the first few ones: press & fill in.
To help you get some context and avoid enabling you with gummy bears and ice cream sandwich code, here are a few scenarios you may encounter while trying to correctly create your first feature for 'double'
Scenario: Double
Given I am on "double" # ERROR
And I fill in "50" for "number"
When I press "Double!"
Then I should see "Result: 100"
Scenario: Double
Given I am on "the double page"
And I fill in "50" for "excellence" # ERROR
When I press "Double!"
Then I should see "Result: 100"
Scenario: Double
Given I am on "the double page"
And I fill in "50" for "number"
When I press "triple Double!" # ERROR
Then I should see "Result: 100"
Scenario: Double
Given I am on "the double page"
And I fill in "50" for "number"
When I press "Double!"
Then I should see "Result: 123" # ERROR
Finally, update test.feature
with the following scenario (or create a new file for it).
Scenario: Double
Given I am on "the double page" # match route in paths.rb!
And I fill in "50" for "number" # match input name
When I press "Double!" # match button value
Then I should see "Result: 100" # match response body on POST
Grab your terminal again and enter $ cucumber
to check your work.
Success! For your own benefit, remember you can apply everything mentioned in the Webrat chapter about Rakefile
, Guardfile
and Procfile
, including HTML generated reports and autotesting with Guard!
That's what you can do with Cucumber. Hate it or love it?