So we’ve been hinting for some time now that we were going to tidy up the
testing helpers we use here, and that’s just what we’ve done. Update: Added
the Why? section below to address rSpec
& Test::Spec
Introducing Shoulda
We had a very hard time coming up with a name for this plugin, mostly because of its somewhat eclectic nature. It does three main things:
Contexts
Shoulda defines context
and should
blocks:
class UserTest < Test::Unit::TestCase
def setup
# Normal setup code
end
def test_can_have_normal_tests
# Normal test here
end
context "a User instance" do
setup do
@user = User.find(:first)
end
should "return its full name"
assert_equal 'John Doe', @user.full_name
end
end
end
This is very much like the rSpec way of doing things, but they can be used along-side normal test method definitions. Also, these context blocks can be nested.
Macros
Shoulda also attempts to collect common test patterns into test macros:
class UserTest < Test::Unit::TestCase
should_require_attributes :name, :phone_number
should_not_allow_values_for :phone_number, "abcd", "1234"
should_allow_values_for :phone_number, "(123) 456-7890"
should_protect_attributes :password
should_have_one :profile
should_have_many :dogs
should_have_many :messes, :through => :dogs
should_belong_to :lover
end
Helpers
Finally, Shoulda adds a few basic-but-useful utility methods and assertions:
assert_difference(User, :count, 1) { User.create }
assert_difference(User.packages, :size, 3, true) do
User.add_three_packages
end
assert_contains(['a', '1'], /\d/)
assert_same_elements([:a, :b, :c], [:c, :a, :b])
Why not rSpec or Test::Unit
A lot of people are going to be wondering why we didn’t just go with one of the many new BDD style testing frameworks that are out there, like Simply BDD, Test::Spec, or the wildly popular rSpec. I don’t want to minimize the work that’s been done on these very cool pieces of software – in fact, Shoulda would never have been written without their example to lead us. But there were some parts of the rest of the plugins that we just didn’t feel right about.
I wrote another post, specifically about rSpec, but here are the major points (most of which apply to all of the plugins):
context
defined on Kernel, andshould
defined on Object – This can make it very hard to extend the framework, and honestly just feels wrong. I can imagine that there would be method definition conflicts between the testing framework and the code being tested. I could be paranoid here, but I didn’t want to find out for sure months down the line.- Extendibility – I think the real value of this testing plugin is in the focused testing macros, which can test large chunks of common code in a single line. The issue above made these kinds of macros hard to write in the other testing frameworks.
- Mocking framework built-in – soon rSpec won’t suffer from this as much as it does now, but I still don’t like the idea of the testing framework and mocking framework being so interlocked.
lambda {}.should.differ(Blog, :count, 1)
– This is purely a matter of taste, but we really didn’t see value in thex.should y
syntax over theassert
syntax.- Compatibility – This one is rSpec specific. As much as I like the format
of the rSpec output, it can cause problems with automated programs that are
expecting Test::Unit output. Also, having to run both
rake spec; rake test
to see all of your tests pass seems like an unnecessary annoyance.
Docs & Installation
Full RDocs are available here, and the official Shoulda page is here.