Bruce Williams asked this question at RailsConf, and I
am soliciting feedback.
Background
First, a little background. There are two possibilities when calling flexmock(). First, you are creating a full mock object:
# Example 1
mock = flexmock("description")
mock.should_receive(...)
A full mock fulfills two roles: (1) it is a target for
should_receive to define expectations, and (2) it is a target for
normal domain messages when testing.
The other possibility is that you are creating a partial mock (i.e. a
regular Ruby object with just a few mocked methods):
The object returned from the flexmock() method is actually a proxy
object that can accept should_receive() messages to define the
expectations, but does not handle normal domain messages. After all, we have a real object that that handles domain messages.
Partial Mocks
It is clear that when creating a partial mock using the non-block form of
flexmock(real_obj), we must return the proxy, else there would be no way to
add expectations. But the return value for the block form of flexmock is
not so clear.
Consider the following code:
# Example 3
real_obj = RealObject.new
result = flexmock(real_obj) do |mock|
mock.should_receive(...)
end
Here the proxy object is passed as the block argument. All the expectation
setup is done within the block. It is very tempting to write this code
as:
# Example 4
result = flexmock(RealObject.new) do |mock|
mock.should_receive(...)
end
But here is the problem: in example 4 we no longer have a reference to the
RealObject instance. The flexmock() method returns the proxy
object, not the real object; just as it does in the non-block form.
Bruce’s Suggestions
Bruce suggested changing the block version of flexmock() to go ahead and
return the real object. Since the proxy is used in the block, there is no
real need for it outside the function. And, I will admit, example 4 is
short and relatively clear, especially with those familiar with the
returning idiom used in Rails.
The Dilemma
So here is my dilemma. Changing FlexMock so that example 4 works properly
is attractive. And I suspect that the return value of flexmock(real_obj) is
not ever used in a significant way in existing code, so backwards
compatibility should be be only a minor concern. However, changing the
return object based on whether or not the method has a block just seems …
wrong.
There is precedent for this. In the standard Ruby libraries
open(fn) and open(fn) { ... } return different things (an
open file for the former and the value of the block for the latter). I’ve
never had problems with this behavior in open, so perhaps I am just being
over sensitive here.
I told Bruce I would blog the issue and consider the feedback received. So
let me know what you think. Should flexmock() be modified to
return the real_object when defining partial mocks using the block form?
You can email me (jim@weirichhouse.org) or add a comment using the comments
link below.