Sinatra Tests 101 - Cucumber (Webrat & Capybara)

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
Cucumber Init 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:

First Cucumber

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á!

Cucumber Webrat success

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.

Webrat HTML

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.

Capybara First Success

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.rbto 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"

Capybara first fail


  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"

Capybara second fail


  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"

Capybara third fail


  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

Capybara fourth fail


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.

Capybara Second Success

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?

Webrat Github Repository

Capybara Github Repository

Tiago Casanova

Developer @ Jumpseller. Currently working with Ruby & JavaScript. Curious and creative on a daily basis and works like a chameleon. Loves hoops and cooks.

Subscribe to Binary Lies

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!