Introducing the Shoulda Testing Plugin

Posted by Tammer Saleh

Apr 06

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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:

1
2
3
4
5
6
7
8
9
10
11
12
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:

1
2
3
4
5
6
7
8
9
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):

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:
1
2
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

Comments on this post

court3nay

Apr 06

court3nay said,

Heh. Haven’t you heard of test/spec?

http://chneukirchen.org/blog/archive/2006/10/announcing-test-spec-0-2-a-bdd-interface-for-test-unit.html

http://poocs.net/2007/4/3/test-spec-on-rails-and-assert_difference

http://weblog.techno-weenie.net/2006/11/24/test-spec-kicks-simply_bdds-ass

Tammer Saleh

Apr 06

Tammer Saleh said,

Hey court3nay,

We looked very closely at test/spec, and almost decided to go with that. There were two things that it, and rSpec, did that we didn’t really agree with:

It defines context and should on Kernel and Object, respectively, which didn’t feel very clean, and (with rSpec, at least) makes it very hard to extend.

It uses the “x.should.equal 3” syntax. This is just a subjective thing, but we didn’t really find that as useful as the regular assert syntax. The lambda example in the assert_difference post you linked to is one in particular that I felt was less clean that just using assert_difference. Same with lambda {}.should.raise Exception. Again, it’s just a matter of taste, and some find that syntax to be more readable.

Tammer Saleh

Apr 06

Tammer Saleh said,

btw: I really loved your tongue-in-cheek icons.

Daniel Fischer

Apr 08

Daniel Fischer said,

Great! Thanks for the plugin, I can see this being put into exceptional use.

Morgan Roderick

Apr 09

Morgan Roderick said,

Looks very interesting!

For those of us using acts_as_authenticated / RESTful authentication, won’t assert_difference clash with the assert_difference defined in AuthenticatedTestHelper?

Or, would we be limited to using just one of the test helpers at a given time, or even worse, do really long prefixes?

Rob Sanheim

Apr 10

Rob Sanheim said,

Looks cool. The macro level test methods look like they’d be worth extracting to their own plugin.

re: test/spec – you can still use traditional asserts_foos—its still just a testcase, after all.

The test_spec on rails extensions that Rick has done are another good reason to go w/ test/spec over alternatives, but hey.

Can you discuss how you test wrt ActiveRecord? All mocks, dynamic fixtures, etc?

Tammer Saleh

Apr 20

Tammer Saleh said,

Morgan:

I’ve never used the acts_as_authenticated plugin, so I hadn’t realized that it defined these methods. There’s been about a dozen implementations of assert_difference floating around on the net, so I’m not too surprised. I’m not entirely sure, but I would expect that whichever plugin is loaded last would override the definition of the other, so things should work just fine. Let me know if you actually experience problems with it.

Rob:

I would have liked to extract this plugin into three different ones, but they all actually make use of each other.

Though rSpec and test/spec can use the regular assert calls, there were the other issues I had outlined as well.


Sorry, comments are closed for this article.

© 2000 - 2009 by thoughtbot, inc.
written by a bushel of tiny robots