Create a gist now

Instantly share code, notes, and snippets.

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

Owner

juliocesar 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 commented Apr 15, 2011

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

vaz 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