Stub Queries and Expect Commands

Posted by James Mead Sat, 23 Dec 2006 20:02:00 GMT

In Making a Mockery of ActiveRecord, a single test has multiple expectations set up for accessor methods…

  specify "should create EmailMessages and Subscriptions when include_subscribers is true" do
    @message.include_subscribers = true
    @message.should_receive(:lists).and_return([@list])
    @message.should_receive(:campaign).twice.and_return(@campaign)
    @list.should_receive(:people).and_return([@person])
    @person.should_receive(:campaigns).and_return([])

    @person.should_receive(:subscriptions).and_return(@subscriptions)
    @subscriptions.should_receive(:create).and_return(nil)
    @campaign.should_receive(:people).and_return([])

    message.generate # I think this line is missing from the original test
    @message.should_have(1).email_messages
  end

I’ve found Nat Pryce’s rule of thumb – stub queries and expect commands – to be very useful in reducing the brittleness of tests (see Yoga for Your Unit Tests).

A query in this context is a method which does not change the state of the object on which it is called. The accessor methods definitely fall into this category, so I would stub them, not set expectations for them. The one command method in the above example which merits an expectation is the call to create on @subscriptions.

However, I prefer to use in-memory ActiveRecord objects instead of mocks or stubs wherever possible. So I’d write something more like this (using Test::Unit and Mocha)...

  def test_should_create_email_messages_and_subscriptions_when_include_subscribers_is_true
    subscriptions = mock()
    person = Person.new(:campaigns => [])
    person.stubs(:subscriptions).returns(subscriptions)
    list = List.new(:people => [person])
    campaign = Campaign.new(:people => [])
    message = Message.new(:lists => [list], :campaign => campaign)
    message.include_subscribers = true

    subscriptions.expects(:create)

    message.generate
    assert_equal 1, message.email_messages
  end

I haven’t actually run this – so it’s quite likely there are errors in it, but you should get the general idea. The person.stubs line is necessary to avoid the type checking that Luke Redpath mentions in his comment.

More thoughts here.

Tags , , , , , ,  | 1 comment

Comments

  1. David Chelimsky said 2 days later:

    Interesting approach. I’ve generally avoided using the real model objects because I don’t like the dependency from my controller test all the way back to ActiveRecord::Base. But this does make things more readable to some extent.

    I wrote about using stubs in setup and mock methods in the specs (tests) themselves. I’m curious to hear your thoughts on that.

    Cheers, David

Comments are disabled