Preview of Latest Mocha Changes
Posted by James Mead Thu, 12 Apr 2007 11:57:00 GMT
I’ve finally managed to find some time to do some serious work on Mocha. Here are some code snippets showing the new functionality available in trunk (revision 128). I don’t don’t know how many people out there are using trunk, but it would be great to get some feedback on these changes before I make a new release. In particular I’d like to know whether…
- I’ve broken anybody’s tests.
- Anybody has a legitimate use for
parameter_blockinExpectation#withthat they don’t think will be handled by a suitable parameter matcher i.e. using the current behaviour where the block is passed the parameters and the result of the block determines whether the expectation matches. I’m planning on deprecating this soon. - Anybody has a legitimate use for passing in an instance of
ProctoExpectation#returnsi.e. using the current behaviour where theProcgets executed to generate a return value. I’m planning on deprecating this soon as well.
I’ve typed this up in a bit of a rush (about to go on holiday for a few days), so apologies if there are any mistakes.
Parameter Matchers
I’ve added a few Hamcrest-style parameter matchers which are designed to be used inside Expectation#with. The following matchers are currently available: has_key(), has_value() & has_entry(). More to follow soon. The idea is eventually to get rid of the nasty parameter_block option on Expectation#with.
object = mock()
object.expects(:method).with(has_key('key_1'))
object.method('key_1' => 1, 'key_2' => 2)
# no verification error raised
object = mock()
object.expects(:method).with(has_key('key_1'))
object.method('key_2' => 2)
# verification error raised, because method was not called with Hash containing key: 'key_1'Values Returned and Exceptions Raised on Consecutive Invocations
Allow multiple calls to Expectation#returns and Expectation#raises to build up a sequence of responses to invocations on the mock. Added syntactic sugar method Expectation#then to allow more readable expectations.
object = mock()
object.stubs(:method).returns(1, 2).then.raises(Exception).then.returns(4)
object.method # => 1
object.method # => 2
object.method # => raises exception of class Exception
object.method # => 4Yields on Consecutive Invocations
Allow multiple calls to yields on single expectation to allow yield parameters to be specified for consecutive invocations.
object = mock()
object.stubs(:method).yields(1, 2).then.yields(3)
object.method { |*values| p values } # => [1, 2]
object.method { |*values| p values } # => [3]Multiple Yields on Single Invocation
Added Expectation#multiple_yields to allow a mocked or stubbed method to yield multiple times for a single invocation.
object = mock()
object.stubs(:method).multiple_yields([1, 2], [3])
object.method { |*values| p values } # => [1, 2] # => [3]Invocation Dispatch
Expectations were already being matched in reverse order i.e. the most recently defined one was being found first. This is still the case, but we now stop matching an expectation when its maximum number of expected invocations is reached. c.f. JMock v1. A stub will never stop matching by default. Hopefully this means we can soon get rid of the need to pass a Proc to Expectation#returns.
object = mock()
object.stubs(:method).returns(2)
object.expects(:method).once.returns(1)
object.method # => 1
object.method # => 2
object.method # => 2
# no verification error raisedThis should still work…
Time.stubs(:now).returns(Time.parse('Mon Jan 01 00:00:00 UTC 2007'))
Time.now # => Mon Jan 01 00:00:00 UTC 2007
Time.stubs(:now).returns(Time.parse('Thu Feb 01 00:00:00 UTC 2007'))
Time.now # => Thu Feb 01 00:00:00 UTC 2007Acknowledgements
Thanks to David Chelimsky, Dan North, Jay Fields, Kevin Clark, Frederick Cheung, James Moore, Brian Helmkamp, Ben Griffiths, Chris Roos & Paul Battley for their input. Apologies to anybody I forgot to mention.

Hi,
This looks really good.
One thing I was thinking today is that it would be great to define an expectation, but let the original method still be called. Something along the lines of:
In other words, you want to check that #bar is called, but not interfere with what it returns.
Maybe there is a way to do this already, or maybe it’s a stupid idea? Thoughts welcome :)
Jon, what use case do you have for such behaviour?
Well, you might not care about the result of the expectation, but the result is needed for the rest of the code to work. In this situation you could stub out the result, but that might be long and complicated so it would be easier to be able to say “just carry on”.
Thanks for your encouraging words about the recent changes to Mocha. I’m sorry it’s taken me so long to respond.
How would stubbing out the result be “long and complicated”? The main point of stubbing the method is usually to provide a “canned” response under the control of the test. I can see that the behaviour you suggest might be useful when you want to set up tests around legacy code i.e. code which you cannot change.
One or two people have asked for this functionality on the Mocha mailing list, but I don’t think it’s really appropriate for Mocha itself. I’ve suggested it might be worth writing a separate library.