Contexts
After the should statements, the most basic building block of shoulda are the contexts. Contexts let you build up a fixture like scenario in which you run your should statements. Context blocks can contain setup/teardown blocks, should blocks, or other context blocks. So let’s start our our test file with a simple context:
class QueueTest < Test::Unit::TestCase
context "A Queue instance" do
setup do
@queue = Queue.new
end
should "respond to :push" do
assert_respond_to @queue, :push
end
end
end
This would produce a test named “test: A Queue instance should respond to :push”, which is nice and readable.
Nesting
This is pretty verbose for simply testing that an instance of Queue responds to :push, but it also sets us up for further contexts and tests. Now I’d like to see that the queue returns whatever is pushed into it. Let’s add a nested context into the mix:
class QueueTest < Test::Unit::TestCase
context "A Queue instance" do
setup do
@queue = Queue.new
end
should "respond to :push" do
assert_respond_to @queue, :push
end
context "with a single element" do
setup { @queue.push(:something) }
should "return that element on :pop" do
assert_equal :something, @queue.pop
end
end
end
end
Producing the test methods “test: A Queue instance should respond to :push” and “test: A Queue instance with a single element should return that element on :pop”.
Note that the setup blocks for the contexts are run in order for each should block. First the @queue instance is created, then :something is pushed into it, and then the assert_equal call is run. All of this happens as if it were part of the same test method. This isn’t exactly what’s going on, but it helps to think of it as something like…
define_method "test: A Queue instance with a single element should return that element on :pop. " do
@queue = Queue.new
@queue.push(:something)
assert_equal :something, @queue.pop
end
Now, if we were using the normal test style, I’d push an element into the queue and test that I get it back all in one test method. But when we divide it into a context for the push and a should block for the test, we can add more tests that would share the same setup.
class QueueTest < Test::Unit::TestCase
context "A Queue instance" do
setup do
@queue = Queue.new
end
should "respond to :push" do
assert_respond_to @queue, :push
end
context "with a single element" do
setup { @queue.push(:something) }
should "return that element on :pop" do
assert_equal :something, @queue.pop
end
should "return 1 on :size" do
assert_equal 1, @queue.size
end
end
end
end
The nesting of contexts can be a powerful tool in your fight to keep your tests dry and readable. The bigger your test files become, the more this refactoring helps out.
assertions in your setups
Another nice feature of the setup blocks are that they are run as part of the test method itself, meaning it’s perfectly fine to put assertions inside them. Now, it wouldn’t be very good practice to fill your setup block with assertions instead of putting them in the should blocks, but it can be a nice tool for sanity checks. For example:
class PersonTest < Test::Unit::TestCase
context "A Person" do
setup { assert @person = Person.find(:first) }
should "rock out" do
assert @person.rocks_out!
end
end
end
It’s much nicer to see a failed assertion in the setup than a whiny_nil exception down the line if the find call failed.
should blocks and contexts are a great starting point for creating test macros. Shoulda comes with a good set of ActiveRecord helpers for making tests clear and concise.