[rspec-users] Specifying mocks

Tom Stuart tom at experthuman.com
Fri May 29 08:51:51 EDT 2009


Hi David,

On 29 May 2009, at 12:10, David Chelimsky wrote:
> Have you read "Mock Roles, not Objects"?
> "its corresponding mock" suggests a single mock object for each real
> implementation. There's nothing stopping you from writing your own
> mock objects to play this role. IMO, this is not what a dynamic mock
> objects framework is for.

Agreed -- I entirely concur that the main benefit of such a framework  
is the ability to speculatively mock collaborators (while thinking in  
terms of their role) before you have implemented them or perhaps even  
know what they are -- but in practice I find that these collaborating  
roles end up falling into one-one correspondence with actual classes,  
which doesn't seem to contradict the core message of "Mock Roles, not  
Objects". Maybe that just indicates I'm not working on designs that  
are sufficiently sophisticated, sufficiently polymorphic or  
sufficiently aspect-oriented to feel the burn of the divergence of  
concepts.

Ultimately I think I'm agreeing with you, but perhaps just using the  
wrong word: I might better have described Account as a role rather  
than necessarily an object/class. The original point applies, namely  
"how can I be sure that my mock (for a given role) is in sync with my  
specification (of that role in the object(s) which perform it)";  
instead of suggesting a single mock object for each real  
implementation, I'm suggesting a single mock object for each role.

And speaking of using the wrong words: maybe what I actually mean is  
"specifying stubs" instead of "specifying mocks", in as much as those  
global helpers I talked about don't really return a "mock" at all,  
just a stub which has been prepared for use as a mock in any spec  
which requires it (cf Spec::Rails' mock_model). But any such spec will  
exercise those stubbed methods which are important for the role's  
behaviour, assuming one expectation per example, so it's important  
that the stubbed behaviour accurately reflects reality, and unless you  
are rigorous about keeping the stubbed behaviour updated to reflect  
the specified behaviour they'll drift out of sync and the specs won't  
be making the right assumptions about collaborators any more.

> Just because two objects sport the same methods doesn't
> mean they behave the same way. In order to verify that a mock Account
> honors the same contract as a real Account, you'd have to have some
> very general specs like "Account balance should be an instance of the
> Money class."

Why? I can't quite get my head around the issues of stubbing stateful  
interactions, which is preventing me from thinking of a good example,  
but what's (abstractly) wrong with:

class Account
   def credit(cents)
     self.balance = self.balance + cents
     return self.balance
   end
end

describe 'Account#credit' do
   before(:each) do
     @account = Account.new(:balance => 100)
   end
   specify { @account.credit(10).should == 110 }
end

def mock_account
   account = mock('Account')
   account.stub!(:credit).with(10).and_return(110)
   return account
end

It seems like there's nothing stopping you stubbing the detailed  
behaviour of Account in a way that will allow the stub to pass the  
specs -- it's just that the stub is only good for the canned  
responses, necessarily a strict subset of the real implementation's  
behaviour over all inputs.

Given the above, I could easily change spec and implementation to

class Account
   def credit(cents)
     self.balance = self.balance - cents
     return self.balance
   end
end

describe 'Account#credit' do
   before(:each) do
     @account = Account.new(:balance => 100)
   end
   specify { @account.credit(10).should == 90 }
end

and the spec will pass, and so will every spec that's using my Account  
stub for mocking Account behaviour, but the system as a whole is  
screwed, right? Because the behaviour's changed but the stub (plus the  
behaviour that is mocked on it by other specs) has stayed the same.

> So really, what we're talking about is concern over method  
> signatures straying.

Sorry, that's not what I meant at all: I intended to talk specifically  
about cases where the signature stays the same but the underlying  
behaviour changes.

> What about when we use mocks in their most powerful way, to specify  
> an object's
> contract with polymorphic collaborators?

In that case there's a role (an object fulfilling that contract) for  
which you might end up creating a shared stub that's used in the  
specifications of all objects which act as the client in that specific  
collaboration. Individual examples in those specifications will set  
individual expectations on that stub in order to specify the  
interaction, presumably clobbering a stubbed method in the process,  
but the remaining stubbed methods will be exercised during each  
example, which gives you a way of knowing that the mocked behaviour  
lines up with what's been canned in the stub.

You can't do much about the detailed, piecemeal mocking that happens  
inside such specs -- aside from the aforementioned reliance upon the  
parts of the stub's canned behaviour for which an expectation hasn't  
explicitly been set, there's no way to check that the expectations  
match up with the specification(s) of the object(s) which performs the  
role you're mocking, so you have to rely on integration specs -- but  
if all of these specs are starting with the same stub, you can at  
least run the role's spec over the stub to make sure it's accurate.  
Why wouldn't you?

> Personal opinion aside, let's say we set out to solve this. We'd need
> some auditing mechanism that says the object being mocked has all the
> same APIs as the mock object.

As I say, that's not what I meant, and it's probably my fault for  
saying "mock". What I meant is that a stub can provide a snapshot of  
all of the behaviour of an object (or role), and I often end up in the  
situation where the same stub is being used as a starting point for  
mocking interactions in lots of specs, but right now there's no  
mechanism for spotting when that stub is spreading misinformation  
because it's become desynchronised from the specification of the  
object (or role) which it represents.

For example: Spec::Rails' mock_model. (Should be called stub_model but  
isn't.) It's very convenient because it stubs out a bunch of  
"behaviour" that you'd otherwise have to stub out yourself in all of  
your model specs before you started adding expectations. It conforms  
to a tiny subset of ActiveRecord::Base's notional specification:  
@record.should_receive(:new_record?).and_return(false) etc. If "the  
ActiveRecord::Base spec" changes, mock_model needs updating, but we  
can't discover that automatically because we never run "the  
ActiveRecord::Base spec" over the object that's returned by  
mock_model. (This is a stupid example because there is no  
ActiveRecord::Base spec, but you get the idea.)

So is my fundamental mistake that I'm being lazy by trying to  
concentrate all of this stub setup in one helper method instead of  
doing piecemeal stubbing in every individual spec that might care? It  
seems superficially like good practice to concentrate all of the  
detail in one place like this, so that when a role's behaviour changes  
you just update one piece of stub setup to reflect the change rather  
than chase around the specs of every single collaborator, but maybe  
that's just serving to obscure the problem that all of the actual  
mocking has to happen in the individual specs and that this is the  
stuff you really care about, not the behaviour of the shared stub?

Cheers,
-Tom


More information about the rspec-users mailing list