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.
In your Gemfile…
group :test do config.gem 'genspec' end
Or, the manual way:
sudo gem install genspec
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 subject.should output("A Help Message") end end with_args :users do it "should generate a UsersController" do subject.should generate("app/controllers/users_controller.rb") { |content| content.should =~ /class UserController/ } end end end
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 subject.should 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 subject.should generate("app/models/user.rb") { |content| content.should =~ /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 subject.should 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 subject.should generate { File.read("app/models/user.rb").should =~ /class User < ActiveRecord\:\:Base/ } end
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 subject.should add_source("http://gems.github.com") end # -or- it "should add a gem source" do subject.should call_action(:add_source, "http://gems.github.com") end # -or- it "should add a gem source" do subject.should 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 subject.should 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 subject.should 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.
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 subject.should output("A Help Message") end # Example 2: Regular Expression it "should generate a help message" do subject.should output(/A [hH]elp Message/) end
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.
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
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 { should generate("app/controllers/welcome_controller.rb") } with_options :behavior => :revoke do it { should delete("app/controllers/welcome_controller.rb") } end end end
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 subject.should generate { File.read("config/routes.rb").should_not 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.
-
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.