In praise of Shoulda macros

Shoulda contexts let you to share setup code between different tests. This is for me one of Shoulda’s most attractive features.

When you combine this with the technique of defining your own macros to encapsulate assertions or setups that come up often, you end up with seriously DRY and readable tests.

I see a few different kinds of Shoulda macros:

Assertion macros

Assertions macros often begin with should_. They encapsulate one or a few assertions.

For ActiveRecord models:

should_require_attributes :name, :phone_number

They may even accept a block and do assertions on its execution, like should_raise:

should_raise(LoadError, :message => /vespene/) do require "more vespene gas" end

To learn more about assertion macros, you can take a look at Shoulda macros allows you to embrace your inner slacker by Josh Nichols. Inner slacker? I’m right there!

Setup macros

This kind of macro encapsulates a setup that comes up often in your test suite. One inspired by Restful Authentication’s login_as helper method could be used like this:

logged_in_as :mat do # Shoulda tests end

These kinds of macros accept a block that defines more Shoulda tests, rather than a block of code testing your app per se.

Turnkey macros

Turnkey macros are beefed up assertion macros. The main difference is their extent. They contain many contexts and a lot of should blocks. They usually accept substantial options hashes or are configured with a setup block. Like should_be_restful in the following example, inspired by the Shoulda documentation:

logged_in_as :stranger do should_be_restful do |resource| resource.create.params = { :subject => "test", :body => "message" } resource.denied.actions = [:edit, :update, :destroy] resource.denied.redirect = "login_url" resource.denied.flash = /only the owner can/i end end

Two Shoulda best practices around setup macros

This article is specifically about setup macros.

Here’s the implementation of a pretty generic Shoulda macro I could define in my test_helper*. This is an implementation of the macro I mentioned at the beginning:

# Sets the current person in the session from the person fixtures. def self.logged_in_as(person, &block) context "logged in as #{person}" do setup do @request.session[:person] = people(person).id end yield end end

Which can then be used like this in any controller test:

logged_in_as :mat do # tests for users end logged_in_as :admin do # tests for admin end

Setup macros have a very subtle catch, however. Here’s a modified version of the first example above:

logged_in_as :mat do setup do @request.session[:last_login] = Time.now end # Some tests end

The setup block you see here is never going to be executed. Why?

If we were to replace the logged_in_as macro by the actual code it contains, here’s what it would look like:

context "logged in as #{person}" do setup do @request.session[:person] = people(person).id end setup do @request.session[:last_login] = Time.now end # Some tests end

Does that make sense? Not so sure.

Shoulda doesn’t like to have multiple setup blocks for a given context. That part does make sense.

Best practice #1: Always describe the situation with a context.

You should always describe the situation in which your test takes place (what your setup is doing) with a context.

logged_in_as :mat do context "with last login set to now" do setup do @request.session[:last_login] = Time.now end # Some tests end end

Fair enough. We blame it on the user of the macro :-)

Since we’re using Ruby, most of us are probably in agreement with Matz’ “Make the programmer happy” motto.

So can we also solve the problem from the other end? Create a setup macro that supports a direct inner setup block? Of course we can, this is Ruby, not VB.

Best practice #2: Create setup macros that support a second setup block

A setup is grafted to a context that describes it. As the creator of the macro, I don’t know what crazy setup blocks programmers will put inside their macro. So I simply create a mute context:

# Sets the current person in the session from the person fixtures. def self.logged_in_as(person, &block) context "logged in as #{person}" do setup do @request.session[:person] = people(person).id end context '' do yield end end end

Now my macro supports the following test without a hitch:

logged_in_as :mat do setup do @request.session[:last_login] = Time.now end # Some tests end

And of course, programmers who stick to best practice #1 can still write a cleaner test without a problem. The awesomeness of contexts lies in the fact that they can be nested:

logged_in_as :mat do context "with last login set to now" do setup do @request.session[:last_login] = Time.now end # Some tests end end

Conclusion

Best practice #1 is simple. A setup block should be described by its encompassing context. It’s a question of readability. Nesting a setup block immediately inside a Shoulda macro is a dubious practice.

Best practice #2 is a more pragmatic solution to the problem. Ok, nesting a block right inside a macro isn’t always the best idea.

But when you don’t have the macro right under your nose, it may take you a while before you think about looking at said macro. I don’t know about you, but I have a tendency to have a great deal of confidence in macros that work well across my test suite.

So after you’ve spent an hour questioning Shoulda (or your sanity, or whether you should have become a gardener instead of a software developer) because your setup block isn’t executing, best practice #2 starts to make sense.

It may or may not be necessary in all your setup macros. I find it’s especially useful for macros that are generic enough to be used across your test suite. Or most of all, in setup macros you will share with the world.

Best practice #2 makes setup macros bulletproof to the problem of multiple setups.

Now go refactor your setup macros!

To learn more about Shoulda, check out Thoughtbot’s comprehensive documentation.