GIANT ROBOTS SMASHING INTO OTHER GIANT ROBOTS

Written by thoughtbot

Introducing the Shoulda Testing Plugin

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, and should 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-insoon 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 the ‘x.should y’ syntax over the ‘assert’ 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.

We’re having some troubles with our plugin repository working with script/plugin at the moment, so svn access will have to suffice:

svn ls http://svn.thoughtbot.com/plugins/shoulda/tags/
svn export http://svn.thoughtbot.com/plugins/shoulda/tags/rel-2.0.4 vendor/plugins/shoulda