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.