AlexRothenberg

Ammeter: The Way to Write Specs for Your Rails Generators

Generators got a complete makeover with Rails 3 making them much easier to write but they’ve been very hard to test if you’re using RSpec.
That’s changed now with the AmmeterGem
which lets you write RSpec specs for your generators.

Who writes generators?

Unless you’ve writing a gem you probably haven’t created a generator, but I bet you’re using one someone else created. If you’ve ever typed

There are a number of resources for writing generators using thor including the
generators guide or
railscast #218 and I’m not going to go into that here.
If you’re using TestUnit, like the rails core team, you can use Generators::TestCase which is part of Rails -
Devise has some good examples.
For those of us using RSpec we can now use Ammeter.

Writing Specs with Ammeter

First you need to tell your gem to use ammeter by 1) adding it to our bundle and 2) making it accessible to our specs.

# <YOUR_GEM_NAME>.gemspecs.add_development_dependency'ammeter'

# spec_helper.rbrequire'ammeter/init'

Then we specify the behavior. We’ll look at an example using Mongoid’s
config generator and
its spec config_generator_spec.
The generator’s usage is rails generate mongoid:config [DATABASE_NAME] [options].

# spec/generators/mongoid/config/config_generator_spec.rbrequire'spec_helper'# Generators are not automatically loaded by Railsrequire'rails/generators/mongoid/config/config_generator'classRails::Application;endclassMyApp::Application<Rails::Application;enddescribeMongoid::Generators::ConfigGeneratordo# Tell the generator where to put its output (what it thinks of as Rails.root)destinationFile.expand_path("../../../../../../tmp",__FILE__)before{prepare_destination}describe'no arguments'dobefore{run_generator}describe'config/mongoid.yml'dosubject{file('config/mongoid.yml')}it{shouldexist}it{shouldcontain"database: my_app_development"}endenddescribe'specifying database name'dobefore{run_generator%w(my_database)}describe'config/mongoid.yml'dosubject{file('config/mongoid.yml')}it{shouldexist}it{shouldcontain"database: my_database_development"}endendend

There’s some boilerplate setup you’ll need at the top of your spec:

Since this spec file is in spec/generators it automatically uses ammeter

Generators are not automatically loaded by Rails’ const_missing so we need to require it explicitly

destination tells the generator where to put its output (we add enough “../”s to get us out of the spec directory)

before { prepare_destination } clears the destination so each spec starts fresh (similar to active record rollback)

Now for the behavior:

run_generator runs the generator (optionally letting you pass in arguments)

file gives you access to a generated file

it should exist makes sure the file was generated

it should contain looks inside a generated file for a string or regex

When you’re generating migrations it is somewhat tricky because migration file names contain a timestamp.
We need another example and will use acts_as_taggable-on’s
migration generator
We could write a spec for this as

# spec/generators/acts_as_taggable_on/migration/migration_generator_spec.rbrequire'spec_helper'# Generators are not automatically loaded by Railsrequire'generators/acts_as_taggable_on/migration/migration_generator'describeActsAsTaggableOn::MigrationGeneratordo# Tell the generator where to put its output (what it thinks of as Rails.root)destinationFile.expand_path("../../../../../tmp",__FILE__)beforedoprepare_destinationRails::Generators.options[:rails][:orm]=:active_recordenddescribe'no arguments'dobefore{run_generator}describe'db/migrate/acts_as_taggable_on_migration.rb'dosubject{file('db/migrate/acts_as_taggable_on_migration.rb')}it{shouldbe_a_migration}endendend

You can see much of the same setup and then the new matcher

it { should be_a_migration } which adds a timestamp to the filename

Why ‘Ammeter’?

An Ammeter is a measuring instrument used to measure the electric current in a circuit.
Generators produce electricity and your specs measure your generators … cute huh :)

Feedback Welcome

Try Ammeter for your generators, I hope you find it useful. If it doesn’t meet your needs fork away - Ammeter is on github.
I welcome any feedback, issues or pull requests.