Mock Commands, Stub Queries
Posted by James Mead Fri, 03 Aug 2007 10:10:00 GMT
Zach Moazeni has just posted a suggested patch for Mocha over on his blog. My understanding of the patch is that it means expectations are verified even when an assertion error occurs in the test. Here is his example…
class Car
def initialize(parts = [])
@parts = parts
end
def start
started = true
@parts.each do | part |
# commenting out for failure
# started = started && part.start
end
started
end
end
class SomeTest < Test::Unit::TestCase
def test_start
engine_mock = mock("engine_mock")
car = Car.new([engine_mock])
engine_mock.expects(:start).returns(false)
assert !car.start
end
end
I’ve had a friendly & useful conversation with Zach about it, but I’m not convinced this is the right way to go. Using the one assertion per test school of thought, you can achieve the same goal by splitting the test into two so you get a test failure for the expectation and another for the assertion…
class SomeTest < Test::Unit::TestCase
def test_should_start_engine
engine = mock('engine')
car = Car.new([engine])
engine.expects(:start)
car.start
end
def test_should_start_if_engine_starts
engine = stub('engine')
car = Car.new([engine])
engine.stubs(:start).returns(false)
assert !car.start
end
end
Something that makes the example less suitable for mocking is that the Car#start method is both a command and a query. If you separate the two, testing with mocks might be easier…
class Car
def initialize(parts = [])
@parts = parts
end
def start
@parts.each { |part| part.start }
end
def started?
@parts.all? { |part| part.started? }
end
end
class SomeOtherTest < Test::Unit::TestCase
def test_should_start_engine
engine = mock('engine')
car = Car.new([engine])
engine.expects(:start)
car.start
end
def test_should_not_be_started_if_engine_is_started
engine = stub('engine')
car = Car.new([engine])
engine.stubs(:started?).returns(false)
assert !car.started?
end
end
I’d be interested to know what other people think…
One thing I do agree with Zach about is that submitting a suggested patch to an open source project is a great way of initiating a constructive conversation.

Hi James.
You can’t always separate commands from queries. It’s a pattern that works well in some contexts but not at all in others.
In particular, it doesn’t work in the face of concurrency where there is a race condition between the command and the query. In those cases you need an atomic operation that performs the command and returns a status with no chance of races.
The solution to this mocks problem is to add an explicit verify before making the assertions. That is, there’s no point in testing the postconditions of an object if the object itself has failed to meet its preconditions.
—Nat
I really should have thought more of about the example before using it. Unfortunately I do know I come across this situation a lot, I’m just failing to articulate the common scenario well (especially while trying to pull out very domain specific knowledge about the code). I hope the concept is not tarnished because of a crappy example on my part.
Being lazy, I don’t always want to explicitly check if my mocks fired correctly. Because if I’m forced to remember:Which prompts me to submit a patch or always include an extension that will do as Nat proposes.
As an aside, for me this brings up an interesting idea in the difference between writing code that is easy to test using my current tools, and writing code that is designed well. Generally the two are in the same court, but I wouldn’t feel comfortable saying that is an absolute.
I’m not knocking the proposed solution in separating command / query up there, I’m more saying that I’d be disappointed if the design is influenced directly or indirectly from the testing tools I use.
-Zach
I’ve been chewing on this a little more, and although I read the article, it just clicked that the
Mock#expectsitself is an assertion. I’ve always understood that, I was just ignorantly discounting that.We have a new project starting up in two weeks. I’m going to try and go down the route of 1 assertion per test and see what my experience is.
I don’t expect it to be a silver bullet, but it may be a different mindset with testing that can help alleviate other troubles I battle. Like keeping the scope of what is actually being tested minimized.
-Zach
Nat – Thanks for pointing out that it’s not always possible to separate methods into commands and queries. Thanks also for the better solution i.e. the explicit verify on the mock before the assertion.
Zach – I quite like Nat’s suggestion of the explicit verify on the mock before the assertion – what do you think of that? I don’t mind when a test makes me change my design – for me this is a big part of TDD. Good luck with the new project.