test/spec on Rails and assert_difference

I have a certain feeling that the style of doing Behavior Driven Development (BDD) sees a little more adoption than, say, a year ago. With RSpec being the more rigorous kid on the block, test/spec by Christian Neukirchen seems to be a little less intruiging. (Yes, RSpec does have its place, too.)

What is test/spec after all?

To quote its website:

test/spec layers an RSpec-inspired interface on top of Test::Unit, so you can mix TDD and BDD (Behavior-Driven Development).

This especially convenient if you’re porting tests of an existing application over from TDD to BDD-style. test/spec blessed tests can be run alongside of Test::Unit based tests and they can even live in the same files if you dare. For a more thorough explanation of the reasoning behind BDD, see its Wikipedia entry.

test_spec_on_rails

Starting out as a simple endorsement by Rick Olson when he discovered test/spec, the test spec on rails plugin recently got a really nice make-over by Per Wigren.

This new version (which, technically, isn’t a version after all – we’re talking Subversion revisions here) provides all sorts of handy wrappers for more Rails-specific assertions, that previously had to be used in all their nakedness and purity before. Here’s a rundown of the new prettyness:


@user.should.validate
@user.should.not.validate

should.redirect_to :action => 'show'
status.should.be :success
template.should.be "foo/show.rhtml"

page.should.select "form#user_form"

Wrappers for assert_difference

If you’ve ever used assert_difference before (the original of which appeared as a code snippet on project.ion.ist), you’re probably unable to live without it. Not having to store before states manually to compare them to an after state in your tests is such a simple, yet effective way to DRY up your tests.


def test_new_publication
  assert_difference(Publication, :count) do
    post :create, :publication => {...}
    # ...
  end
end

But now that you’ve been drinking the test/spec kool-aid, what’s that assert_something call doing in your test? Well, what about this:


context 'Blog' do
  specify 'should create blog' do
    lambda {
      Blog.create :title => '...'
    }.should.change(Blog, :count)
  end
end

Admittedly, the lambda { } part isn’t the sexiest on the planet. But it fits nicely into the rest of the “should” specifications. Oh, and there’s a “not” part, too!

Here’s the code that makes this work (assuming you’ve installed the test_spec gem1 and the test_spec_on_rails plugin2). Just put it at the bottom of test/test_helper.rb.


# Credits: http://project.ioni.st/post/218#post-218
module Test::Unit::AssertDifference
  def assert_difference(object, method = nil, difference = 1)
    initial_value = object.send(method)
    yield
    assert_equal initial_value + difference, object.send(method), "#{object}##{method}"
  end

  def assert_no_difference(object, method, &block)
    assert_difference object, method, 0, &block
  end
end

# Extension for test/spec/rails, wraps assert_difference
module Test::Spec::Rails
  module ShouldDiffer
    def differ(*args)
      assert_difference(*args, &@object)
    end
    alias :different :differ
    alias :change :differ
  end
  module ShouldNotDiffer
    def differ(*args)
      assert_no_difference(*args, &@object)
    end
    alias :different :differ
    alias :change :differ
  end
end

Test::Spec::Should.send(:include, Test::Unit::AssertDifference)
Test::Spec::Should.send(:include, Test::Spec::Rails::ShouldDiffer)
Test::Spec::ShouldNot.send(:include, Test::Unit::AssertDifference)
Test::Spec::ShouldNot.send(:include, Test::Spec::Rails::ShouldNotDiffer)

Have fun refactoring your tests!


  1. sudo gem install test_spec
  2. script/plugin install http://svn.techno-weenie.net/projects/plugins/test_spec_on_rails/
Filed Under: Rails