Project

genspec

0.02
No commit activity in last 3 years
No release in over 3 years
Simple, expressive Thor and/or Rails 3+ generator testing for RSpec. For the Rails 2.3 version, use genspec 0.1.x.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

Runtime

< 4, >= 2
>= 0
 Project Readme

Simple, expressive generator testing for RSpec. This version of GenSpec supports testing either Thor generators (in standalone applications/gems) or Rails 3 generators for Rails apps.

For the Rails 2.3 version, use genspec 0.1.x. Note that it is no longer actively maintained; only bug fixes will be committed to the Rails 2.3 version of this gem.

Installation¶ ↑

In your Gemfile…

group :test do
  config.gem 'genspec'
end

Or, the manual way:

sudo gem install genspec

Usage¶ ↑

Just like rspec-rails uses the structure of your spec/ directory to infer which test is being run (controllers, helpers, lib, etc.), you just need to create a spec/generators directory and put your generator specs in there. A basic generator spec might look something like this:

# in spec/generators/custom_controller_spec.rb
require 'spec_helper'

describe :custom_controller do
  context "with no arguments or options" do
    it "should generate a help message" do
      expect(subject).to output("A Help Message")
    end
  end

  with_args :users do
    it "should generate a UsersController" do
      expect(subject).to generate("app/controllers/users_controller.rb") { |content|
        expect(content).to =~ /class UserController/
      }
    end
  end
end

Checking an engine¶ ↑

You may need to specify a base class, particularly if you are testing a Rails engine:

describe "base_class:custom_controller" do
  ...
end

Checking Generated Files¶ ↑

This is the preferred way to test files that were generated, because this matcher checks your generator’s behavior. The test won’t care how a file is generated, as long as it is generated. It’s as simple as passing the name of the file you expected to be generated:

it "should generate a readme file" do
  expect(subject).to generate("README")
end

You can also check the generated file’s content by simply passing a block. The content argument in the block is a simple String containing the content of the file:

it "should generate a model called 'user'" do
  expect(subject).to generate("app/models/user.rb") { |content|
    expect(content).to =~ /class User < ActiveRecord\:\:Base/
  }
end

You can also very simply ensure that the generator runs without error, without any further validation, by omitting all arguments:

it "should generate successfully" do
  expect(subject).to generate
end

Finally, you could pass a block but no other arguments to generate in order to check the generator’s results the old-fashioned way:

it "should generate a model called 'user'" do
  expect(subject).to generate {
    expect(File.read("app/models/user.rb")).to =~ /class User < ActiveRecord\:\:Base/
  }
end

Checking Generator Actions¶ ↑

This is the most intrusive form of generation matching. While powerful, it will also make your tests brittle, because there’s a high likelihood that even minor changes to your generators will require you to update the spec to match.

However, sometimes you need to verify that some action occurs which can’t be validated using the methods above. You can use the generation method matcher for this.

All 3 of the following examples perform exactly the same test. Use whichever seems the most expressive to you. (I prefer the first one.)

it "should add a gem source" do
  expect(subject).to add_source("http://gems.github.com")
end

# -or-
it "should add a gem source" do
  expect(subject).to call_action(:add_source, "http://gems.github.com")
end

# -or-
it "should add a gem source" do
  expect(subject).to generate(:add_source, "http://gems.github.com")
end

You can stop passing arguments at any time. This has the effect of widening the range of acceptable parameters. For instance, the following example does the same thing but will accept any source URL, as long as the add_source action is called:

it "should add a gem source" do
  expect(subject).to generate(:add_source)
end

Similarly, you can get away with specifying only the some of a sequence of arguments; the omitted arguments will accept any value, while the specified ones will be tested. Another example:

it "should inject into file" do
  expect(subject).to inject_into_file("config/environment.rb", "config.gem :thor")
end

# if the generator includes the following action, the test will
# pass even though the +after+ option wasn't specified in the spec:
#
# inject_into_file "config/environment.rb", "config.gem :thor",
#                  :after => "Rails::Initializer.run do |config|\n"
#

You can test in this way using any public instance method in the Thor::Actions, Rails::Generators::Actions or Rails::Generators::Migration modules. You can change this behavior by modifying the GenSpec::Matchers::GenerationMethodMatchers::GENERATION_CLASSES array.

Checking for Output¶ ↑

If you need to test the generator’s feedback rather than the generator’s results, you can use the output matcher to assert that your generator has produced some specific content in its output. This is helpful for making sure your help message is accurate, for instance.

# Example 1: String
it "should generate a help message" do
  expect(subject).to output("A Help Message")
end

# Example 2: Regular Expression
it "should generate a help message" do
  expect(subject).to output(/A [hH]elp Message/)
end

More Advanced Usage¶ ↑

Preparing Input¶ ↑

Sometimes your generator needs to prompt for input. For instance, maybe it’s encountered a file that is about to be overwritten and needs to check whether the user really wants to commit to the changes. You can prepare input streams like so:

with_input "y\n" do
  it "should do something" do
    # . . .
  end
end

with_input <<-end_input do
  y
  n
  a
end_input
  it "should do a particular set of somethings" do
    # . . .
  end
end

Of course, preparing an input stream requires for you to know in advance which questions the generator will be asking, but your specs should be testing exactly this behavior, so this is not an issue.

Specifying Arguments¶ ↑

You can pass any combination of command line arguments or options to your generator using with_args. For instance, to pretend the –verbose option was passed, we could use the following spec:

describe :custom_controller do
  with_args "--verbose" do
    it "should produce verbose output" do
      # . . .
    end
  end
end

Here is another example using with_args:

describe :custom_controller do
  with_args :users, :index, :new, :edit do
    it "should produce an index action" do
      # . . .
    end
  end
end

Note that no matter what you specify as arguments, by default they’ll be initially converted to an array of Strings because that’s what gets passed into the generator if you run it from the command line. You can bypass this behavior by passing an :object => true option as the last argument:

describe :custom_controller do
  with_args MyFancyObject.new, :object => true do
    # . . .
  end
end

Finally, you can also choose to use with_args without a block, in which case it will be applied to the current context:

describe :custom_controller do
  context "a Users controller with index, new, and edit actions" do
    with_args :users, :index, :new, :edit

    # . . .
  end
end

Passing Options¶ ↑

Sometimes you need to change the behavior of the generator itself, by passing options directly into the generator instance that you couldn’t normally pass from the command line. A perfect example is when you want to test what would happen when the generator’s behavior is set to :revoke, which is equivalent to the +rails destroy+ command.

Here’s an example that verifies that a file is created by the generator, but that the same file is deleted when the generator’s behavior is set to :revoke:

describe :controller do
  with_args "welcome" do
    it { is_expected.to generate("app/controllers/welcome_controller.rb") }

    with_options :behavior => :revoke do
      it { is_expected.to delete("app/controllers/welcome_controller.rb") }
    end
  end
end

Fixtures¶ ↑

Most generators will assume you have some basic file structure in place. For instance, a controller generator that automatically adds routes for the controller may assume that a config/routes.rb file exists prior to running it. Constructing a dummy file structure prior to testing is a necessity in such scenarios. Luckily, GenSpec provides an easy way to do just that:

describe :custom_controller do
  within_source_root do
    mkdir_p "config"
    touch "config/routes.rb"
  end

  # . . .
end

You can even nest such structures within various contexts:

describe :custom_controller do
  within_source_root { mkdir_p "config" }

  context "with a routes file" do
    within_source_root { touch "config/routes.rb" }

    it "should insert the new route" do
      expect(subject).to generate {
        expect(File.read("config/routes.rb")).not_to be_blank
      }
    end
  end
end

Fixture generation will always happen in the same order – from the top-level context to the bottom-level context – which means you are free to build the dummy file system incrementally, as needed, without worrying about order of operation.

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 © 2010-2011 Colin MacKenzie IV. See LICENSE for details.