Steve Hodgkiss

Testing Rails 3 generators with RSpec

March 01, 2011

Rails provides an easy way to test generators if you’re using test/unit, but what can you do if you’re using RSpec?

class InstallGeneratorTest < Rails::Generators::TestCase
  tests InstallGenerator
  destination File.expand_path("../../tmp", __FILE__)
  setup :prepare_destination
  test "Generates an initializer" do
    run_generator
    assert_file "config/initializers/mygem.rb"
  end
end

You don’t really want to use test unit just to test generators. One approach is to use cucumber to drive aruba through the actions of generating a new Rails app, running bundle install, running your generator then verifying it did the right thing. The problem with this approach is that it’s really, really slow. At least it was a few months ago when I tried it. bundle install isn’t exactly known for it’s speed, though technically that’s not bundlers fault.

It depends on the functionality you’re testing, but for the actions that most generators perform like adding files to a project, running it in a separate process with Aruba, generating a fresh rails app and running bundle install seems like overkill. You get more assurance that it will work that way, because it’s a full stack integration test doing the same thing the user will do, it’s just going to be slow. The approach Rails::Generators::TestCase takes is to simply run the generator in the current process against a temporary directory. There are trade off’s whichever way you choose, but for the average generator I’d settle for less integration and more SPEED!

Today I released generator_spec for testing generators with RSpec. It’s basically a thin wrapper around Rails::Generators::TestCase, with the same assertions and setup methods available. It comes with an awesome file matcher DSL too.

require "generator_spec/test_case"
describe InstallGenerator do
  include GeneratorSpec::TestCase
  destination File.expand_path("../../tmp", __FILE__)
  
  before do
    prepare_destination
    run_generator
  end
  
  specify do
    destination_root.should have_structure {
      no_file "non_existant.rb"
      directory "config" do
        directory "initializers" do
          file "mygem.rb" do
            contains "# Change this"
          end
        end
      end
      directory "db" do
        directory "migrate" do
          migration "create_tests" do
            contains "class TestMigration"
          end
        end
      end
    }
  end
end

More information can be found with the code on Github.