Freelancing Gods 2014

God
02 Sep 2011

Combustion - Better Rails Engine Testing

I spent a good part of last month writing my first Rails engine – although it’s not yet released and for a client, so I won’t talk about that too much here.

Very quickly in the development process, I was looking around on how to test Rails engines. It seemed that, beyond some basic unit tests, having a full Rails application within your test or spec directory was the accepted approach for integration testing.

That felt kludgy and bloated to me, so I decided to try something a little different.

The end goal was full stack testing in a clear and manageable fashion – writing specs within my spec directory, not a bundled Rails app’s spec directory. Capybara’s DSL would be nice as well.

This, of course, meant having a Rails application to test through – but it turns out you can get away without the vast majority of files that Rails generates for you. Indeed, the one file a Rails app expects is config/database.yml – and that’s only if you have ActiveRecord in play.

Enter Combustion – my minimal Rails app-as-a-gem for testing engines, with smart defaults for your standard Rails settings.

Setting It Up

A basic setup is as follows:

  • Add the gem to your gemspec or Gemfile.
  • Run the generator in your engine’s directory to get a small Rails app stub created: combust (or bundle exec combust if you’re referencing the git repository instead).
  • Add Combustion.initialize! to your spec/spec_helper.rb (currently only RSpec is supported, but shouldn’t be hard to patch for TestUnit et al).

Here’s a sample spec_helper, mixing in Capybara as well:

require 'rubygems'
require 'bundler'

Bundler.require :default, :development

require 'capybara/rspec'

Combustion.initialize!

require 'rspec/rails'
require 'capybara/rails'

RSpec.configure do |config|
  config.use_transactional_fixtures = true
end

Putting It To Work

Firstly, you’ll want to make sure you’re using your engine within the test Rails application. The generator has likely added the hooks we need for this. If you’re adding routes, then edit spec/internal/config/routes.rb. If you’re dealing with models, make sure you add the tables to spec/internal/db/schema.rb. The README covers this a bit more detail.

And then, get stuck into your specs. Here’s a really simple example:

# spec/controllers/users_controller_spec.rb
require 'spec_helper'

describe UsersController do
  describe '#new' do
    it "runs successfully" do
      get :new

      response.should be_success
    end
  end
end

Or, using Capybara for integration:

# spec/acceptance/visitors_can_sign_up_spec.rb
require 'spec_helper'

describe 'authentication process' do
  it 'allows a visitor to sign up' do
    visit '/'

    click_link 'Sign Up'
    fill_in 'Name',     :with => 'Pat Allan'
    fill_in 'Email',    :with => 'pat@no-spam-please.com'
    fill_in 'Password', :with => 'chunkybacon'
    click_button 'Sign Up'

    page.should have_content('Sign Out')
  end
end

And that’s really the core of it. Write the specs you need to test your engine within the context of a full Rails application. If you need models, controllers or views in the internal application to fully test out your engine, then add them to the appropriate location within spec/internal – but only add what’s necessary.

Rack It Up

Oh, and one of my favourite little helpers is this: Combustion’s generator adds a config.ru file to your engine, which means you can fire up your test application in the browser – just run rackup and visit http://localhost:9292.

Caveats

As already mentioned, Combustion is built with RSpec in mind – but I will happily accept patches for TestUnit as well. Same for Cucumber – should work in theory, but I’m yet to try it.

It’s also written for Rails 3.1 – it may work with Rails 3.0 with some patches, but I very much doubt it’ll play nicely with anything before that. Still, feel free to investigate.

And it’s possible that this could be useful for integration testing for libraries that aren’t engines. If you want to try that, I’d love to hear how it goes.

Final Notes

So, where do we stand?

  • You can test your engine within a full Rails stack, without a full Rails app.
  • You only add what you need to your Rails app stub (that lives in spec/internal).
  • Your testing code is DRYer and easier to maintain.
  • You can use standard RSpec and Capybara helpers for integration testing.
  • You can view your test application via Rack.

I’m not the first to come up with this idea – after I had finished Combustion, it was pointed out to me that Kaminari’s test suite does a similar thing (just not extracted out into a separate library). It wouldn’t surprise me if others have done the same – but in my searching, I kept coming across well-known libraries with full Rails apps in their test or spec directories.

If you think Combustion could suit your engine, please give it a spin – I’d love to have others kick the tires and ensure it works in a wider set of situations. Patches and feedback are most definitely welcome.

12 Mar 2010

Using Thinking Sphinx with Cucumber

While I highly recommend you stub out your search requests in controller unit tests/specs, I also recommend you give your full stack a work-out when running search scenarios in Cucumber.

This has gotten a whole lot easier with the ThinkingSphinx::Test class and the integrated Cucumber support, but it’s still not perfect, mainly because generally everyone (correctly) keeps their database changes within a transaction. Sphinx talks to your database outside Rails’ context, and so can’t see anything, unless you turn these transactions off.

It’s not hard to turn transactions off in your features/support/env.rb file:

Cucumber::Rails::World.use_transactional_fixtures = false

But this makes Cucumber tests far more fragile, because either each scenario can’t conflict with each other, or the database needs to be cleaned before and after each scenario is run.

Pretty soon after I added the inital documentation for this, a few expert Cucumber users pointed out that you can flag certain feature files to be run without transactional fixtures, and the rest use the default:

@no-txn
Feature: Searching
  In order to find things as easily as possible
  As a user
  I want to search across all data on the site

This is a good step in the right direction, but it’s not perfect – you’ll still need to clean up the database. Writing steps to do that is easy enough:

Given /^a clean slate$/ do
  Object.subclasses_of(ActiveRecord::Base).each do |model|
    next unless model.table_exists?
    model.connection.execute "TRUNCATE TABLE `#{model.table_name}`"
  end
end

(You can also use Database Cleaner, as noted by Thilo in the comments).

But adding that to the start and end of every single scenario isn’t particularly DRY.

Thankfully, there’s Before and After hooks in Cucumber, and they can be limited to scenarios marked with certain tags. Now we’re getting somewhere!

Before('@no-txn') do
  Given 'a clean slate'
end

After('@no-txn') do
  Given 'a clean slate'
end

And here’s a bonus step, to make indexing data a little easier:

Given /^the (\w+) indexes are processed$/ do |model|
  model = model.titleize.gsub(/\s/, '').constantize
  ThinkingSphinx::Test.index *model.sphinx_index_names
end

So, how do things look now? Well, you can write your features normally – just flag them with no-txn, and your database will be cleaned up both before and after each scenario.

My current preferred approach is adding a file named features/support/sphinx.rb, containing this code:

require 'cucumber/thinking_sphinx/external_world'

Cucumber::ThinkingSphinx::ExternalWorld.new

Before('@no-txn') do
  Given 'a clean slate'
end

After('@no-txn') do
  Given 'a clean slate'
end

And I put the step definitions in either features/step_definitions/common_steps.rb or features/step_definitions/search_steps.rb.

So, now you have no excuse to not use Thinking Sphinx with your Cucumber suite. Get testing!

29 Apr 2008

Link: DanNorth.net » What’s in a Story?

Good overview of BDD, especially in relation to stories (feature in rspec)

06 Apr 2008

Link: New in Rails: a request profiler for profiling your app | redemption in a blog

Old news, but I need to remember it's in there

13 Dec 2007

Rspec'ing controllers

I’m always trying to find a better way to write specs for my Rails apps with RSpec – it took a while for me to be comfortable with writing model specs, but just recently, I’ve developed a style of controller specs that I feel work well for me.

While it’s not too hard to write methods that automate some of the repetitive side of things, it can be hard to do so in a manner that fits RSpec’s DSL – but I’ve found the key to (my current style of) controller specs is shared behaviours. An example of a few actions from the news controller at ausdwcon.org:

describe NewsController, "index action" do
  before :each do
    @method = :get
    @action = :index
    @params = {}
  end
  
  it_should_behave_like "Public-Access Actions"
  
  it "should paginate the results" do
    @news = []
    News.stub_method(:paginate => @news)
    
    get :index
    
    News.should have_received(:paginate)
    assigns[:news].should == @news
  end
  
  it "should set the title to 'News'" do
    News.stub_method(:paginate => [])
    
    get :index
    
    assigns[:title].should == "News"
  end
end

describe NewsController, "new action" do
  before :each do
    @method = :get
    @action = :new
    @params = {}
  end
  
  it_should_behave_like "Admin-Only Actions"
  
  it "should set the title to 'News'" do
    controller.current_user = User.stub_instance(:admin? => true)
    
    get :new
    
    assigns[:title].should == "News"
  end
end

You can find the full spec for the controller on pastie.caboo.se. The shared behaviors – ‘Public-Access Actions’ and ‘Admin-Only Actions’ – are (at least for the moment) kept in my spec_helper.rb file – a sample of which is below:

describe "Admin-Only Actions", :shared => true do
  it "should not be accessible without authentication" do
    @controller.current_user = nil
    
    send @method, @action, @params
    
    response.should be_redirect
    response.should redirect_to(new_session_url)
  end
  
  it "should not be accessible by a normal user" do
    @controller.current_user = User.stub_instance(:admin? => false)
    
    send @method, @action, @params
    
    response.should be_redirect
    response.should redirect_to(new_session_url)
  end
  
  it "should be accessible by an admin user" do
    @controller.current_user = User.stub_instance(:admin? => true)
    
    send @method, @action, @params
    
    response.should_not redirect_to(new_session_url)
  end
end

Firstly – the not so nice stuff: the use of instance variables to communicate the method, action and parameters of requests to the shared behaviours. It’s not ideal, and apparently there’s plans to add arguments to the it_should_behave_like method, but for the moment it does the job.

I’m using Pete Yandell’s NotAMock for my stubbing – albeit with a few modifications of my own (which may make it back into the plugin itself at some point). I also use my own ActiveMatchers – but that’s more focused on models. It’s also not really feature-complete, but if you like what it offers, feel free to use it.

Oh, and the main caveat? This is my current way of spec’ing controllers – and it’s vastly better than the minimal specs I was writing before this – but it may/will change. I don’t even know if my style is ‘best practice’ – I’m putting them online to get feedback and provoke discussion. So please feel free to critique.

05 Dec 2007

Link: Plain Text Stories: Part III

Examples of the new stories/integration testing in rspec

29 Aug 2007

Link: smartbomb - Hornsby

Scenario-focused spec'ing, instead of fixtures

07 May 2007

Link: Beware of the Dog | JoeDog / Siege

10 Jan 2007

Link: eigenclass - rcov: code coverage for Ruby

19 Dec 2006

Link: Kwala

19 Dec 2006

Link: Unit Testing Guidelines

Tags:

17 Dec 2006

Link: Monkey Test : Rails testing made even easier.

27 Nov 2006

Link: Wood for the Trees » Blog Archive » Superfast Testing How To: In-Memory SQLite3

25 Oct 2006

Link: San Francisco, Sqlite3 :memory: Tests, Asteroids

01 Sep 2006

Link: Ruby, Rails, Test::Rails Cheat Sheet | Ruby on Rails for Newbies

30 Aug 2006

Link: Fear of Fish—@jamie.van.dyke

23 Aug 2006

Link: Colorize Your Tests

02 Aug 2006

Link: Rails Plugin: rails_rcov | Archives | codablog | Coda Hale

02 Aug 2006

Link: New Bamboo - TDD - You Never Know When The Brakes Might Fail

03 Jul 2006

Link: Rails - Assert Valid Markup Tests

RssSubscribe to the RSS feed

About Freelancing Gods

Freelancing Gods is written by , who works on the web as a web developer in Melbourne, Australia, specialising in Ruby on Rails.

In case you're wondering what the likely content here will be about (besides code), keep in mind that Pat is passionate about the internet, music, politics, comedy, bringing people together, and making a difference. And pancakes.

His ego isn't as bad as you may think. Honest.

Here's more than you ever wanted to know.

Ruby on Rails Projects

Other Sites

Creative Commons Logo All original content on this site is available through a Creative Commons by-nc-sa licence.