Preface
When you develop some cool gem that should work in different ruby frameworks
you definitely should write acceptance tests.
Nowadays it’s pretty easy to do with RSpec+Capybara.
Your goal
Assume you develop MyRackMiddleware gem that should work in Sinatra and Rails application.
Your actions
Create gem layout
To create layout use well-known bundle gem command:
$ bundle gem my_rack_middleware
It’s generate something like:
├── lib │ ├── my_rack_middleware │ │ └── version.rb │ └── my_rack_middleware.rb ├── Gemfile ├── my_rack_middleware.gemspec └── Rakefile
Add to Gemfile :
source "http://rubygems.org" gemspec gem 'rspec' gem 'capybara' gem 'sinatra' gem 'rails'
Now start use BDD :) Before start implementation we need two sample applications.
Let’s put they into apps directory.
Creating Sinatra test application
Thus Sinatra is simple framework it’s trivial to create test application:
$ cat apps/test_sinatra_app.rb
require 'sinatra'
class TestSinatraApp < Sinatra::Base
enable :sessions
get '/login' do
body "Please log in"
end
post '/login' do
session[:user_email] = params[:user_email]
redirect to('/profile')
end
get '/profile' do
if user_email = session[:user_email]
body "Welcome, #{user_email}!"
else
redirect to('/login')
end
end
end
Creating Rails3 test application
When you think that with rails it will be hard then your are wrong :)
Thanks to rails3 design its also rather simple! Let’s see:
$ cat apps/test_rails_app.rb
require 'rails'
require 'action_controller/railtie'
module TestRailsApp
class Application < Rails::Application config.secret_token = '572c86f5ede338bd8aba8dae0fd3a326aabababc98d1e6ce34b9f5' routes.draw do get '/login' => 'test_rails_app/sessions#new'
post '/login' => 'test_rails_app/sessions#create'
get '/profile' => 'test_rails_app/profiles#show'
end
end
class SessionsController < ActionController::Base def new render :text => "Please log in"
end
def create
session[:user_email] = params[:user_email]
redirect_to '/profile'
end
end
class ProfilesController < ActionController::Base def show if user_email = session[:user_email] render :text => "Welcome, #{user_email}!"
else
redirect_to '/login'
end
end
end
end
So you have whole rails application in one file. Nice! :)
Write integration tests
Now is most interesting part of this article.
Capybara gem itself has rspec helpers that allows you to write rspec examples
in way of acceptance testing (aka feature and scenario).
Actually internally feature is just alias to ExampleGroup (describe) and scenario is alias to Example (it).
But this syntax follows you to think different.
Put in spec/spec_helper.rb
require 'capybara/rspec'
require 'rack_session_access'
require 'rack_session_access/capybara'
# load test applications
Dir[File.expand_path('../../apps/*.rb', __FILE__)].each do |f|
require f
end
# configure sinatra application to use MyRackMiddleware
TestSinatraApp.configure do |app|
app.use MyRackMiddleware
end
# configure rails application to use MyRackMiddleware
TestRailsApp::Application.configure do |app|
app.middleware.use MyRackMiddleware
end
Write acceptance spec for example in spec/middleware_spec.rb :
Follow DRY principle and use shared_examples!
require 'spec_helper'
shared_examples "common scenarios" do
scenario "test application itself" do
page.visit "/login"
page.should have_content("Please log in")
page.visit "/profile"
page.should have_content("Please log in")
end
scenario "MyRackMiddleware related scenario ..." do
...
end
end
feature "MyRackMiddleware", %q(
As ROLE
I want FEATURE
So I have BENEFIT
) do
context "with Sinatra application" do
background do
Capybara.app = TestSinatraApp
end
include_examples "common scenarios"
end
context "with Rails application" do
background do
Capybara.app = TestRailsApp::Application
end
include_examples "common scenarios"
end
end
Run tests
Before implementation run specs
$ bundle exec rspec -fs -c spec/middleware_spec.rb
Now you may add scenarios that cover your MyRackMiddleware influence on applications and start your middleware implementation!
Conclusion
We hope this article will help you to write better tests for your gems.
