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