Shoulda gem is great not only because it provides you with a very clean and natural way of organizing tests with context and should building blocks, but it also comes with quite a large set of predefined macros that mirror Rails’ validations, relations etc. What’s even better — it’s very easy to create your own macros to further speed up test writing. Modern versions of Shoulda gem allow to do it in a clean and modular way. That’s great news if you are serious about TDD because for every substantial codebase you will end up with even bigger pile of testing code, so any tool helping in encapsulating common test logic or patterns is priceless.

This article is a tutorial on writing custom Shoulda macros: from very simple to quite complex.



A note on structure

First things first: your custom macros should be placed in a Ruby file in test/shoulda_macros/ directory (assuming it’s a Rails project). You can have as many files as you like so I recommend grouping macros by their theme or usage. Having small, coherent modules of related macros makes for easier reuse in other projects.

Inside the file you basically add class methods to Test::Unit::TestCase so the basic pattern is:

class Test::Unit::TestCase def self.should_be_awesome should "be awesome" do # actual test code here end end # even more Shoulda awesomeness... end

The methods you create here can either call various assert_... methods or use Shoulda’s building blocks should and context . The latter approach is recommended (read: totally cool) because then your macros will fit nicely into Shoulda framework, providing nice test names and informative failure messages.

Simple macro

So, how about a simple macro for starters? For example, let’s write a macro to assert that the tested class has an attr_reader :

class Test::Unit::TestCase def self.should_have_attr_reader name klass = self.name.gsub(/Test$/, '').constantize should "have attr_reader :#{name}" do obj = klass.new obj.instance_variable_set("@#{name}", "SomeSecretValue") assert_equal("SomeSecretValue", obj.send(name)) end end end # Usage class PostTest should_have_attr_reader :comments_number end

First (line 3) we need to get the class under test. Next line is where things get interesting — a should block is created. Please note how we include the name parameter in the block’s description. This way we get more information in case of failure and can actually use the macro more than once per class (otherwise Shoulda would complain about duplicate test names). Inside the should block there’s the actual testing code, which is irrelevant to this tutorial, so I’ll spare you the details. Just remember to actually assert something inside :)

Reusing built-in & custom macros

As an additional bonus, we can reuse our macros in other macros, so had we create a should_have_attr_writer in a similar manner, we could also get:

def self.should_have_attr_accessor name should_have_attr_reader name should_have_attr_writer name end

We can also reuse built-in macros, for example to create should_validate_inclusion_of , which for some reason is not available in Shoulda. Of course, in general it’s not possible to test all the values that should not be allowed, but I decided to bite the bullet and came up with this:

def self.should_validate_inclusion_of a, opts lst = opts[:in] should_allow_values_for a, *lst rng = (lst.min.pred..lst.max.succ) invalid = (rng.to_a + [nil, "", 0, 1, 2]) - lst should_not_allow_values_for a, *invalid end # Usage class NumberTest should_validate_inclusion_of :powers_of_two, :in => [1, 2, 4, 8, 16] end

In lines 2 & 3, we get our list of allowed values and hand it to Shoulda’s own should_allow_values_for . So far so good, but how to get invalid values? Again, this is not relevant to this tutorial, but in short I assume that the values are numbers and create a little bit wider list then remove the valid values. That gives us a list of values that are not allowed for the attribute and all we need to do is call should_not_allow_values_for on them.

should_have_attr_reader revisited

Of course we are not limited to one should block per macro, so we can improve our should_have_attr_reader to accept any number of params:

class Test::Unit::TestCase def self.should_have_attr_reader *names klass = self.name.gsub(/Test$/, '').constantize names.each do |name| should "have attr_reader :#{name}" do obj = klass.new obj.instance_variable_set("@#{name}", "SomeSecretValue") assert_equal("SomeSecretValue", obj.send(name)) end end end end # Usage class PostTest should_have_attr_reader :yes, :i_really, :have, :that, :many, :attr_readers end

The only thing new here is each loop on line 4 that creates as many should blocks as there are parameters given to our macro. This is better that one should block with a loop iterating over parameters inside it, because all attr_reader s are tested independently and one failure doesn’t prevent others from running.

Advanced

And now for even more contrived advanced example. This time it’s should_delegate which tests if some methods are delegated (Rails style) to some field:

def self.should_delegate *args if args.last.is_a? Hash opts = args.pop field = opts[:to] args.each { |name| should_delegate name, field } return else name, field = args end klass = self.name.gsub(/Test$/, '').constantize to_class = field.to_s.camelize.constantize should "delegate #{name} to #{to_class}" do to = to_class.new to.expects(name).returns "name_value" obj = klass.new field => to assert_equal("name_value", obj.send(name)) end end # Usage class Comment belongs_to :post delegate :category, :to => :post end class CommentTest # simple case should_delegate :category, :post # "advanced" case should_delegate :category, :more, :attrs, :to => :post end

In the simple case one should block is created to test if the method in question is delegated to the field. In the “advanced” case, there are more attributes and the field is specified as the last argument preceded by a :to => to make it more DSL-ish. With these arguments, should_delegate pops the hash off the argument list, extracts the :to option and then calls itself recursively on all remaining arguments, thus reusing the “simple case”.

One-off cases

Defining custom macros doesn’t have to be done in separate files. If your test logic is specific to only one method or class, you can define them right there in the test class. I do it whenever I notice some pattern emerge in my tests that is too specific for a full-blown custom macro. For example, here’s a context block for some helper method:

context "class_for_value" do should "equal 'bullet_cross_icon' for false" do assert_equal('bullet_cross_icon', class_for_value(false)) end should "equal 'bullet_cross_icon' for 0" do assert_equal('bullet_cross_icon', class_for_value(0)) end should "equal 'tick_icon' for true" do assert_equal('tick_icon', class_for_value(true)) end should "equal 'tick_icon' for 1" do assert_equal('tick_icon', class_for_value(1)) end should "equal nil for 2" do assert_equal(nil, class_for_value(2)) end should "equal nil for 10" do assert_equal(nil, class_for_value(10)) end end # class_for_value

Pretty boring and tedious to write and maintain. Why not refactor this with Customo Macroculus?

context "class_for_value" do def self.should_equal expected, value should "equal '#{expected}' for #{value}" do assert_equal(expected, class_for_value(value)) end end should_equal 'bullet_cross_icon', false should_equal 'bullet_cross_icon', 0 should_equal 'tick_icon', true should_equal 'tick_icon', 1 should_equal nil, 2 should_equal nil, 10 end

Ah, much better now. And that concludes this mini tutorial. Be sure to check out http://wiki.github.com/thoughtbot/shoulda/example-macros and http://mileszs.com/blog/2008/09/21/shoulda-macros-for-common-plugins.html for more examples, including creating contexts inside your macros. There are many collections of useful Shoulda macros on Github and the Internet, try googling — maybe there’s even one to do what you need? You wouldn’t have to read this boring tutorial then :)