Testing gem integration with multiple ruby frameworks

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.

References