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.
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.
I think that in BankAccount#transfer, the line "balance -= amount" should instead read "self.balance -= amount".
Post a comment
Required fields in bold.