Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Testing front-end for a Sinatra app with RSpec and Capybara

Testing front-end for a Sinatra app with RSpec and Capybara

I've used Cucumber quite a bit on my last job. It's an excellent tool, and I believe readable tests are the way to the future. But I could never get around to write effective scenarios, or maintain the boatload of text that the suite becomes once you get to a point where you have decent coverage. On top of that, it didn't seem to take much for the suite to become really slow as tests were added.

A while ago I've seen a gist by Lachie Cox where he shows how to use RSpec and Capybara to do front-end tests. That sounded perfect for me. I love RSpec, I can write my own matchers when I need them with little code, and it reads damn nicely.

So for my Rails Rumble 2010 project, as usual, I rolled a Sinatra app and figured I should give the idea a shot. Below are my findings.

Gemfile

Starting with the Gemfile, just so you can see what gems I'm using for this exactly.

source :rubygems

gem 'sinatra',      '1.0'
gem 'thin',         '1.2.7'
gem 'mongoid',      '2.0.0.beta.19'
gem 'bson_ext',     '1.1.1'
gem 'haml',         '3.0.21'
gem 'bcrypt-ruby',  '2.1.2', :require => 'bcrypt'
gem 'json_pure',    '1.4.6', :require => 'json/pure'

group :development do
  gem 'sinatra-reloader', '0.5.0'
end

group :test do
  gem 'rspec',            '2.0.0'
  gem 'faker',            '0.3.1'
  gem 'machinist',        '2.0.0.beta2'
  gem 'machinist_mongo',
    :require  => 'machinist/mongoid', 
    :git      => 'git://github.com/nmerouze/machinist_mongo.git',
    :branch   => 'machinist2'
  gem 'capybara',         '0.3.9'
end

Rakefile

Location: ROOT/Rakefile.

So we can run "rake spec" and get the suite to run.

require 'rubygems'
require 'rspec/core/rake_task'

RSpec::Core::RakeTask.new do |task|
  task.rspec_opts = ["-c", "-f progress", "-r ./spec/spec_helper.rb"]
  task.pattern    = 'spec/**/*_spec.rb'
end

spec_helper.rb

Location: ROOT/spec/spec_helper.rb.

This is the file that bootstraps the testing environment. Every spec will require this first.

require 'bundler/setup'
require 'sinatra'
Sinatra::Application.environment = :test
Bundler.require :default, Sinatra::Application.environment
require 'rspec'
require 'machinist'
require 'machinist/mongoid'
require File.dirname(__FILE__) + '/../config/boot'

RSpec.configure do |config|
  config.before(:each) { Machinist.reset_before_test }
end

Store.blueprint do
  name        { Faker::Company.name }
  email       { Faker::Internet.email }
  password    { 'test123' }
  description { Faker::Lorem.paragraph(1 + rand(3)) }
  location    { Faker::Address.street_address }
end

acceptance_helper.rb

Location: ROOT/spec/acceptance_helper.rb.

Loads the Sinatra app file itself (the one where your routes are), Capybara, and it also features some helpers which we'll use on the front-end specs, both to improve readability and keep them short.

require File.dirname(__FILE__) + '/spec_helper'
require Sinatra::Application.root + '/app'
disable :run

require 'capybara'
require 'capybara/dsl'

Capybara.app = Sinatra::Application

RSpec.configure do |config|
  config.include Capybara
end

# Helpers
def signup store
  visit '/signup'
  fill_in 'email',  :with => store.email
  fill_in 'name',   :with => store.name
  fill_in 'description',  :with => store.description
  fill_in 'location',     :with => store.location
  click 'Create my account'
end

def signin email, password
  visit '/'
  # within('#sign-in') do
    fill_in 'email', :with => email
    fill_in 'password', :with => password
    click 'Sign in'
  # end
end

def selector string
  find :css, string
end

The specs

I've placed all my front-end specs in ROOT/spec/acceptance. I would run them individually through the TextMate bundle at all times, so I had no need to make a separate rake task just for that.

Plus on deploy I wanted to make sure that everything passed, so having those get picked up by "rake spec" was handy.

Sample: login_required_spec.rb

Location: ROOT/spec/acceptance/login_required_spec.rb.

A sample spec that ensures that certain pages that are password protected will, upon being visited without a valid session, direct the user to the front page where the login form is.

I couldn't some of the matchers to work (happy to hear from you if you do), so there's room for improvement here.

require File.dirname(__FILE__) + '/../acceptance_helper'

describe 'URLs that require login' do  
  context 'the /mystore URL' do
    before :each do
      visit '/logout'
    end

    it "lets you in if you're logged in" do
      store = Store.make! :password => 'test123'
      signin store.email, 'test123'
      visit '/mystore'
      selector('#already-have-account').should be_nil
    end

    it "redirects you to the home page if you're not logged in" do
      visit '/mystore'
      selector('#already-have-account').should_not be_nil
    end
  end
end

Done!

It turned out to be a lot easier than I expected, but I couldn't get some of the matchers to work. E.g.: page.should have_selector. Happy to hear from you if you figure it out.

I assume you can simply change the Capybara driver to Selenium, and have specs for testing front-end behavior as well. Which is quite awesome.

If you have any questions, ping me on Twitter or leave a comment.

@ashtija

This comment has been minimized.

Copy link

commented Apr 14, 2011

Thank you. This works fine, but I am trying to work with selenium (Sinatra, Rspec, Capybara and Selenium) but I am getting "Rack application timed out during boot" and its not executing browser :(. Please help

@juliocesar

This comment has been minimized.

Copy link
Owner Author

commented Apr 14, 2011

Hey. Capybara offers a DSL for doing integration tests which I think is what you should use rather than following this guide.

Check https://github.com/jnicklas/capybara.

@ashtija

This comment has been minimized.

Copy link

commented Apr 15, 2011

Thanks, I was able to run test successfully on firefox. But it takes hell of a time to execute.

@vaz

This comment has been minimized.

Copy link

commented May 19, 2016

This post is pretty old but still shows up pretty high in search results... so:

Regarding getting the matchers like have_selector to work, you need another include:

config.include Capybara::DSL  # used to be just Capybara, that's deprecated now
config.include Capybara::RSpecMatchers

You should only need to require capybara/rspec in the spec helper.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.