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.

* A note on where to define Shoulda macros. Shoulda 2 can now auto load macros that are in the right location. This will help you keep your test_helper cleaner. Read more about it succinctly in Shoulda can automatically load custom macros by Josh Nichols or in the Shoulda 2.0 release post.

If you use Shoulda and, like me, you hate Test::Unit’s assert_raise(), I may have something of interest for you.

Why the hate?

Well, assert_raise accepts an *args list of exception types.

If you don’t pass any, you get some nonsense because an empty array doesn’t jive with the exception raised by your block. Useful. So if you don’t care what exception is raised, assert_raise isn’t gonna help you.

Also, assert_raise doesn’t let you specify what kind of exception message you’re expecting. I actually don’t mind that a given assertion should verify exactly one thing. On the other hand I have to jump through hoops to capture the exception if I want to assert on the error message.

A shoulda macro to the rescue

As usual, Shoulda is there to help us keep our test code DRY and intuitive. I’ve concocted a useful macro called should_raise. Here’s the gist:

It must be called with the block you expect to raise an exception, of course. You can also specify two optional arguments, the exception type and the message.

:kind_of or :instance_of

If not specified, an assertion is made that an exception was raised, but with no restriction on the type of the exception.

If you specify :kind_of, the assertion will be that much more precise. It will check that the exception raised was of the type specified, or a descendant.

If you specify :instance_of, the assertion is now that the exception raised was exactly of the type specified.

A shorthand is also available, where the type of the exception is supplied directly, like should_raise(LoadError), in which case the assertion is the same as with :instance_of.

In all of these cases, exactly one assertion is generated, whether or not :type is specified.

:message

If :message is specified, a second assertion will be added in order to make sure the error message matches the parameter. This can either be a string or a regex. The assertion is simply an assert_match.

If not specified, no assertion is generated for the message.

Examples

should_raise do
  require "more vespene gas"
end
# 1 assertion

should_raise(LoadError) do
  require "more vespene gas"
end
# 1 more restrictive assertion

should_raise(:instance_of => LoadError) do
  require "more vespene gas"
end
# 1 assertion, the same as should_raise(LoadError)

should_raise(:kind_of => ScriptError) do
  require "more vespene gas"
end
# 1 assertion, slightly less strict than with :instance_of (note: LoadError < ScriptError)

should_raise(:message => "no such file to load") do
  require "more vespene gas"
end
# 2 assertions

should_raise(:message => /vespene/) do
  require "more vespene gas"
end
# 2 assertions

should_raise(LoadError, :message => "such file to load") do
  require "more vespene gas"
end
# 2 assertions

should_raise(:kind_of => LoadError, :message => "file to load") do
  require "more vespene gas"
end
# 2 assertions

should_raise(:instance_of => LoadError, :message => "to load") do
  require "more vespene gas"
end
# 2 assertions

Conclusion

As you can tell, I’m eagerly awaiting Starcraft II.

No, I meant: check out the code on gist 20019. I’ve included a reasonable suite of unit tests in a comment at the end.

Feel free to use it in any way you like. Just make sure you don’t sue me.

I don’t know about you, but for me, using git is so low-friction that I use it basically for everything where I may need a powerful undo button. In other words, I don’t use it only for team software development projects.

For example, I’ve frequently used it in the past to keep track of the modifications I make to an article I work on for few days. God knows I write a lot of these. There’s a reason I called this blog Programblings ;-)

To be honest, I’m using git as I write even this short article.

I also use it for trivial one evening coding projects. As soon as I spend more than an hour on code, whatever it is, I’ll usually create a local git repo for it.

One of the annoying things I realized when creating repositories more and more often, is that I always ended up ignoring the same files. Over and over again. Boring.

Fortunately for me, git can be configured to take into consideration a global ignore file. Heck, I can even create a system-wide ignore file if I want (check out git config’s doc for more info).

Configure your personal ignore file

I like to stick to conventions so I call my file .gitignore, and I put it in my home directory. But that’s up to you, really.

git config --global core.excludesfile ~/.gitignore

Note that there’s one little gotcha to be aware of. If you prefer to edit the .gitconfig file directly (or if you use a weird shell), git expects an absolute path. In the example above, bash converted the ~ shorthand to my home directory.

Now I just add ignore globs to it like any other project level (directory level, really) git ignore file.

echo .DS_Store >> ~/.gitignore

Once I’ve ignored all my favorite useless files, I can get cracking and never worry about them again.

I still have to ignore files

When I create a new repository on which people may actually contribute, I’ll still create a proper ignore file, however. Otherwise I’d convey the message that I consider contributors as slaves who only deserve the boring work of creating ignore files. Since I’m pure of heart, that’s not how I roll.