At the Forge - Testing Rails Applications with Shoulda

New to testing? Just want an easier time with Test::Unit? Shoulda is the answer.

but the success won't really tell you much, other than the fact that you need to write some tests.

Testing with and without Shoulda

Now comes the hard part. What sorts of tests do you want to write? Well, that depends on the constraints you have put on your model, typically by using ActiveRecord validations.

Specifically, you presumably will want to make sure that the people have a first and last name, and that their grade in school (for the purposes of demonstrating some additional testing) is greater than 0 and less than 13. You will want to make sure that the person's birth date is in the past. You also will want to make sure that every e-mail address in the system is unique to avoid having more than one person with the same e-mail address.

In the model file itself, the validations will look like this:

class Person < ActiveRecord::Base
  validates_presence_of :firstname, :lastname, :email_address
  validates_uniqueness_of :email_address
  validates_numericality_of :grade_in_school, 
  ↪:greater_than_or_equal_to => 0, :less_than_or_equal_to => 13

If you simply were using Test::Unit, you probably would want to test each of these validations. This has less to do with testing the validations and more to do with ensuring that your code meets the specifications you have laid out. (If tests were only a means of checking the correctness of your code, you could make a pretty good argument against tests for these validations, because ActiveRecord already has a fairly extensive test suite.)

If you were to try to test this line:

validates_presence_of :firstname, :lastname, :email_address

you would need to iterate over each of the three fields that are mentioned, checking to see whether the model would be valid if one of these were missing. See Listing 1 for an example of what person_test.rb, the file that contains the unit tests for the Person object, would look like just to test the need for each of those.

But, you lose something in creating these verbose tests. Instead of functioning as a checkup on your code, and as a specification of sorts for what you intend to do, these tests become verbose, repetitive and difficult to read.

With Shoulda installed, you now can remove all of the test cases that are shown in Listing 1, replacing them with one simple invocation:

should_validate_presence_of :firstname, :lastname, :email_address

Shoulda comes with a large number of macros that can help you test your ActiveRecord models in this way. For example, you can test all of the validations defined for the Person model using Shoulda macros:

should_validate_presence_of :firstname, :lastname, :email_address
should_validate_uniqueness_of :email_address
should_validate_numericality_of :grade_in_school
should_ensure_value_in_range :grade_in_school, (1..12), 
 ↪:low_message => 'must be greater than or equal to 1', 
 ↪:high_message => 'must be less than or equal to 12'

Notice how the Shoulda macros' names reflect the names of the ActiveRecord validators. This was done after Shoulda was first released, which means that some of the documentation you see on-line might be slightly out of date and include deprecated macro names.

Also notice that in order to ensure that grade_in_school is numeric and that it is within a certain range, conditions that are set by a single validation line might sometimes require more than one Shoulda macro. In the particular case that I demonstrate here, there was a surprising mismatch between the error message that Rails gave to Shoulda and the message that Shoulda was expecting, in checking to see that the person's grade in school is in an acceptable range. In the end, I got around the problem by telling Shoulda what messages to expect from Rails. Although this is more verbose than I might have liked, it demonstrates the flexibility Shoulda offers.

Not surprisingly, Shoulda's authors make it possible for you to create your own macros, much as you might create your own validator method for an ActiveRecord class. I don't go into creating such macros here, but it is fairly well documented, and it means you can create a large number of tests, package them together under a single Shoulda macro and then use those tests (via the macro) across one or more projects.