RSpec 2 add-on for specifying and testing generators

This project contains RSpec 2 matchers, helpers and various utilities to assist in writing Generator specs. There is additional support for writing specs for Generators in Rails 3.

Why?

Rails 3 has a Rails::Generators::TestCase class for use with Test-Unit, to help test generators. This TestCase contains specific custom assertion methods that can be used to assert generator behavior. To create an RSpec 2 equivalent, I wrapped Rails::Generators::TestCase for use with RSpec 2 and created some RSpec 2 matchers that mimic the assertion methods of the Test-Unit TestCase. I have also a bunch of "extra goodies" to the mix.

This RSpec DSL should make it very easy and enjoyable to spec and test your Generators with RSpec 2 :)

Feedback

Please let me know if you find any issues or have suggestions for improvements.

Install

gem install generator-spec

The gem is a jewel based on jeweler. To install the gem from the code, simply use the jeweler rake task:

rake install

Usage

The following demonstrates usage of this library. There are many more options and DSL convenience methods (see wiki, code or specs).

Configuration

First setup the spec_helper.rb. Here is an example configuration.

# spec/spec_helper.rb
  
require 'rspec'
require 'generator-spec'

# configure it like this to use default settings
RSpec::Generator.configure do |config|
  config.debug = false
  config.remove_temp_dir = true
  config.default_rails_root(__FILE__)
  config.logger = :stdout # :file to output to a log file, logger only active when debug is true
end

# or customize the location of the temporary Rails 3 app dir used
RSpec::Generator.configure do |config|
  # ...
  config.rails_root = '~/my/rails/folder'
end

Specs for generators

I recommend having a separate spec file for each generator (generator specs). You can use the special require_generator statement to ensure that one or more generators are loaded and made available for a given spec.

require_generator :canable

This will load the generator : generators/canabale_generator.rb

If the generator is in a namespace (subfolder of generators), use a nested approach like this:

require_generators :canable => ['model', 'user']

This will load the generators: generators/canable/model_generator.rb and generators/canable/user_generator.rb

You can also load generators from multiple namespaces and mix and match like this. I recommend against this however as it is difficult to read.

require_generators [:canable => ['model', 'user'], :other => :side, :simple]

Auto-require all generators

You can also require all generators or all within a specific namespace like this:

require_generators :all require_generators :canable => :all

Example: full generator spec

# spec/generators/model_generator_spec.rb  

require 'spec_helper'

# list of generators to spec are loaded
require_generator :canable

describe 'model_generator' do
  # include Rails model helpers for ActiveRecord
  # available: 

  # Other ORM options - :mongo_mapper, :mongoid and :data_mapper
  # note: use_orm auto-includes the :model helper module
  use_orm :active_record
  
  # load helper modules and make available inside spec blocks
  # here the module in rails_helpers/rails_migration is included 
  # to load multiple helpers use the method -  use_helpers
  use_helper :migration
  
  before :each do              
    # define generator to test
    setup_generator 'model_generator' do
      tests Canable::Generators::ModelGenerator
    end    
    # ensure clean state before each run
    remove_model :account
  end

  after :each do              
    # ensure clean state after each run  
    remove_model :account
  end

  describe "the weird stuff!!!" do
    before :each do
      @generator = with_generator do |g|   
        g.run_generator :account.args
      end
    end
    
    it "should not work without an existing Account model file" do            
      @generator.should_not generate_file :account, :model
    end
  end
    
  it "should not work without an existing Account model file" do            
    with_generator do |g|   
      g.run_generator :account.args
      g.should_not generate_file :account, :model
    end
  end

  it "should decorate an existing Account model file with 'include Canable:Ables'" do            
    with_generator do |g|  
      create_model :account     
      g.run_generator 'account'.args
      g.should generate_model :account do |content|
        content.should have_class :account do |klass|
          klass.should include_module 'Canable::Ables'
        end
      end
    end
  end
end

Code specs

There are a bunch of specialized ruby code matchers in the matchers/content folder which can be used to spec code files in general. Check out the specs in spec/generator_spec/matchers/content for examples on how to use these.

Rails specs

The rails_helpers folder contains a bunch of files which makes it easy to spec rails files and to perform various "Rails mutations". These mutations make it easy to setup the temporary Rails app in a specific pre-condition, which is required for a given spec.

Examples: Rails helpers

require File.expand_path(File.dirname(FILE) + '/../../../spec_helper')

describe 'controller' do include RSpec::Rails::Controller

before :each do
create_controller :account do %q{ def index end } end
end

after :each do
remove_controller :account end

it "should have an account_controller file that contains an AccountController class with an index method inside" do
Rails.application.should have_controller :account do |controller_file| controller_file.should have_controller_class :account do |klass| klass.should have_method :index end end end end

Note on Patches/Pull Requests

  • Fork the project.
  • Make your feature addition or bug fix.
  • Add tests for it. This is important so I don't break it in a future version unintentionally.
  • Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
  • Send me a pull request. Bonus points for topic branches.

Copyright (c) 2010 Kristian Mandrup. See LICENSE for details.