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.
