David Chelimsky

random thoughtlessness

rspec and autotest

At RailsConf 2007 in Portland, I had the privilege of sitting down w/ Ryan Davis and working with him to improve the runtime relationship between RSpec and Autotest. David Goodlad was at the table as well, and helped me work out some of the mappings so autotest runs the right examples when you change application code.

The result is that with RSpec >= 1.0.3 and ZenTest >= 3.6.0, you can now use Autotest with RSpec on your Ruby projects, Rails or otherwise, simply by typing “autotest” in the project root. No additional plugins necessary. Sweet.

One gotcha: Some have reported that when an example fails, autotest keeps running it over and over again until you get it to pass. This is due to a conflict between RSpec’s and autotest’s mechanisms for narrowing down the set of files to run. This is easily resolved by removing the following lines from spec/spec.opts:

--format
failing_examples:previous_failures.txt
--example
previous_failures.txt

If you run ‘script/generate rspec’, these lines will not be included in the generated spec.opts file. Otherwise you can just delete them yourself.

raise_controller_errors

Controller examples in Spec::Rails used to implicitly raise errors by overriding rescue_action like this:

<code>def rescue_action(e) do |e| raise e; end</code>

This requires that you explicitly override rescue_action in your controllers if you want to see errors being raised. To make this a bit easier, I just added a new method (trunk r2041 – will be released in 1.0.4) for controller examples that lets you do this:

<code>describe SomeController do
before(:each) do
  raise_controller_errors
end
...
end
</code>

This causes the rescue_action above to get defined on the controller class at runtime. And to make it even easier, spec/spec_helper.rb now calls this by default:

<code>Spec::Runner.configure do |config|
...
config.before(:each, :behaviour_type => :controller) do
  raise_controller_errors
end
...
end
</code>

So rescue_action will be defined by default, but you roll your own by deleting those lines in spec_helper.rb.

an introduction to RSpec - Part I

Here’s an introductory tutorial for those of you interested in getting started with RSpec.

Background

Behaviour Driven Development is an Agile development process that comprises aspects of Acceptance Test Driven Planning, Domain Driven Design and Test Driven Development. RSpec is a BDD tool aimed at TDD in the context of BDD.

You could say that RSpec is what is traditionally known as a unit testing framework, but we prefer to describe it as “a Domain Specific Language for describing the expected behaviour of a system with executable examples.”

Also, the process that this tutorial guides you through is Test Driven Development at its core, but we use words like “behaviour” and “example” instead of “test case” and “test method”.

To understand why we choose this nomenclature, and more detailed history of BDD and RSpec’s evolution, check out the writings of “Dan North”:http://dannorth.net/tags/agile/bdd/, “Dave Astels”:http://daveastels.com/articles/2005/07/05/a-new-look-at-test-driven-development, and “Brian Marick”:http://exampler.com/.

Throughout the tutorial, I’ll address some of the philosophy behind various choices that are made, but you’ll have a much better understanding of them (or at least a context to put them in) if you peruse these recommended readings.

don’t let :name_prefix result in the name of an existing model

Here’s something that bit me today. I’ll bet it’s documented somewhere, and it makes perfect sense, but maybe I can help you avoid this in case you missed the docs like I did.

The situation

I’m working on an asset management system which includes Categories and Tags, which have a many-to-many relationship expressed by a CategorizedTag model.

<code>class Category < ActiveRecord::Base
has_many :tags, :through => :categorized_tags
has_many :categorized_tags
end

class Tag < ActiveRecord::Base
has_many :categories, :through => :categorized_tags
has_many :categorized_tags
end

class CategorizedTag < ActiveRecord::Base
belongs_to :category
belongs_to :tag
end
</code>

The problem

In routes.rb, I wanted to nest tags inside categories:

<code>  map.resources :categories do |categories|
  categories.resources :tags, :name_prefix => 'categorized_'
end
</code>

This seemed fine, but I got a nil-pointer error on categorized_tags_path in a view.

The fix

Guessing that there was a naming conflict with CategorizedTag, I tried this instead (‘category’ instead of ‘categorized’):

<code>  map.resources :categories do |categories|
  categories.resources :tags, :name_prefix => 'category_'
end
</code>

Sure enough, category_tags_path worked just fine!

The moral

So make sure that when you use :name_prefix that it doesn’t result in the name of an existing model.

RSpec-0.9 is finally released

We finally released RSpec-0.9 today. We had a little trouble w/ 0.9.0, so we skipped ahead to 0.9.1.

To get the gem:

gem install rspec

To install the Spec::Rails plugin:

ruby script/plugin install svn://rubyforge.org/var/svn/rspec/tags/REL_0_9_1/rspec
ruby script/plugin install svn://rubyforge.org/var/svn/rspec/tags/REL_0_9_1/rspec_on_rails

This release comprises myriad improvements and changes, some of which I’ve blogged about here, the rest of which you can read about on the rspec website.

Of special note is our growing list of contributors. It is very exciting to be involved with a project that has such an active user group filled with imaginative people who have contributed some of RSpec’s most exciting new features.

Thank you, thank you, thank you to all who participate whether it is through patches, feature requests, bug reports, or simply interacting on the mailing lists. You’ve all had an important impact on RSpec’s growth.

RSpec-0.9.1 and Autotest (ZenTest-3.5.2)

Autotest (part of ZenTest) now supports RSpec. This is fantastic news! For those of you who do not know about autotest, it is a program that runs in the background while you are writing your tests and code. Each time you make a change it automatically reruns your tests – and now your specs, too! This is a powerful addition to the TDD/BDD experience.

Recent releases of both tools overlapped a bit so there are changes in RSpec-0.9.1 that are not reflected yet in ZenTest. Also, while ZenTest-3.5.2 supports Spec::Rails, RSpec’s Ruby on Rails plugin, it does not support non-Rails Ruby projects.

I’ve submitted a patch to the ZenTest project which addresses both of these issues. Until the patch is applied, or the issues are addressed in some other way, you can apply it yourself to get autotest working with RSpec for Rails and other projects. These steps work on a mac. I assume that the commands are quite similar for Linux and Cygwin users.

  1. Go to http://rubyforge.org/frs/?group_id=419 and download ZenTest-3.5.2.tgz

  2. Unpack the tar and

    tar zxvf ZenTest-3.5.2.tgz cd ZenTest-3.5.2

  3. Get and install the patch

    curl -O http://blog.davidchelimsky.net/files/ZenTest-3.5.2-rspec.patch patch -p0 < ZenTest-3.5.2-rspec.patch

  4. Build and install the gem

    rake gem sudo gem install pkg/ZenTest-3.5.2.gem

Once you’ve built and installed the patched gem, you run autotest as normal. Stand in the root of your project and say:

autotest

If you have a spec directory at the root of your project, autotest will load up rspec_rails_autotest for Rails projects and rspec_autotest for everything else.

To quote Josh Knowles, Happy (Auto)Specing!

predicate_matchers

Updated on 5/2/2007

In RSpec-0.8 if you say …

<code>File.should_exist(path)
</code>

… the expectation passes if File.exist?(path). Here’s how that should look in RSpec-0.9, with the underscore removed:

<code>File.should exist(path)
</code>

Supporting this for any arbitrary predicate would require more method_missing magic than we were willing to stomach, so we added a means of easily declaring methods like this yourself. We’ve supplied #exist out of the box, but you can add your own with a simple declaration.

Here’s how you do this for an individual behaviour:

<code>describe Fish do
predicate_matchers[:swim] = :can_swim?
it "should swim" do
  Fish.new.should swim
end
end
</code>

And here’s how you define them globally, so they are available in every example in your suite:

<code>Spec::Runner.configure do |config|
config.predicate_matchers[:swim] = :can_swim?
end
</code>

rspec 0.9 - plug in any mock framework

RSpec-0.9 lets you work with the mock framework of your choice. It not only ships with adapters for mocha and flexmock, but it also provides you an easy entry point to plug in another framework of your choosing – or even your creation.

rspec, mocha or flexmock

RSpec is the default mock framework, so if you want to use RSpec’s mock framework you need not set anything up. If you want to use mocha or flexmock, just say

<code>Spec::Runner.configure do |config|
config.mock_with :mocha
end
</code>

or

<code>Spec::Runner.configure do |config|
config.mock_with :flexmock
end
</code>

Of course, if you’re using mocha or flexmock you have to install those gems, but you don’t need to require them because that is taken care of for you implicitly.

Other mocking frameworks

If you have another mocking framework that you like to use, or one that you are developing yourself, you’ll need to create an adapter for it like this:

<code>module MyMockFrameworkAdapter
def setup_mocks_for_rspec
  # Called before any #before(:each) blocks - use
  # this to set up any necessary hooks to your system.
end
def verify_mocks_for_rspec
  # Called after any #after(:each) blocks.
  # NOTE - your mocks should fail by raising an error.
end
def teardown_mocks_for_rspec
  # Called after verify_mocks_for_rspec. This
  # is guaranteed to run, even if there
  # are failures.
end
end
</code>

And then include it using the new configuration system:

<code>Spec::Runner.configure do |config|
config.mock_with MyMockFrameworkAdapter
end
</code>

Inferred Controllers and Helpers in Spec::Rails

Here’s a nice little enhancement in Spec::Rails-0.9.

Up until now, controller examples required that the controller be named:

<code>#the old way
context "Login Controller" do
controller_name :login
...
</code>

You’ll still be able to do that, but you’ll also be able to do this:

<code>describe LoginController do
...
</code>

… and Spec::Rails will assume that LoginController is what you want to use.

This works for your helper examples as well:

<code>describe PeopleHelper do
...
</code>