Adam @ Heroku
a tornado of razorblades

One Expectation Per Spec

Posted by Adam Wiggins on March 15, 2008 at 02:17 PM

Jay Fields posts about one expectation per spec, something that I generally agree with but often find hard to practice. I typically find myself with one mock and one assert per test, such as this example from the rush specs:

it "transmits file_contents" do
  @con.should_receive(:transmit).with(:action => 'file_contents', :full_path => 'file').and_return('contents')
  @con.file_contents('file').should == 'contents'
end

I want to test both the input and the output - that the file_contents method calls the right method with the right arguments, and that it returns the expected value. Breaking this into two specs would be:

it "transmits file_contents" do
  @con.should_receive(:transmit).with(:action => 'file_contents', :full_path => 'file')
  @con.file_contents('file')
end

it "gets the right return value from file_contents" do
  @con.stub!(:transmit).and_return('contents')
  @con.file_contents('').should == 'contents'
end

This is a lot more verbose but I don't feel it adds a whole lot of clarity. Checking both the input and the output in one place seems reasonable to me. But I'll keep this in the back of my head and see how it influences my spec-writing.

Another item I spotted in Jay's example specs is stub_everything. I wasn't previously aware of this. (His examples use Mocha, but the RSpec mocks have the same exact method.) Like this:

class BankAccount
  def transfer(other_account, amount)
    balance -= amount
    other_account.balance += amount
  end
end

it "transfers money out of this account"
  @account.balance = 10
  @account.transfer(stub_everything, 1)
  @account.balance.should == 9
end

stub_everything returns an object that responds to every possible method, but does nothing on the calls. This allows you to effectively ignore any operations on that object, rather than having to stub every call explcitly.

Tags: bdd
Hierarchy: previous, next

Comments

There are 2 comments on this post. Post yours →

You might want to mention that the method is not really a method, unless I am talking about a different way to achieve this. By creating a mock and passing in :nullobject => true, you achieve the same outcome. This also works for Rails Spec's mockmodel.

Roman Le Négrate

I think that in BankAccount#transfer, the line "balance -= amount" should instead read "self.balance -= amount".

Post a comment

Required fields in bold.