[rspec-users] Specifying mocks

Matt Wynne matt at mattwynne.net
Sat May 30 04:10:02 EDT 2009


On 29 May 2009, at 13:51, Tom Stuart wrote:

> 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?

On my team, we've built up a StubFactory which works a lot like the  
FactoryGirl plug-in and creates 'stock' stubbed objects which we use  
when we need a generic stub of a given role. This is a useful tool,  
and it saves a lot of noise in our tests when we just need something  
that quacks enough like an Account or whatever in order to let a test  
run through, so that you can focus on the detail of the *specific*  
collaboration behaviour you're concerned about in that test.

So that's one part of this - I definitely think it's pragmatic and  
valuable to factor out common mocking setup into a single place if  
that's what you seem to be duplicating a lot in your tests.

The auditing though... I've been around the loop with this one myself  
- before I really discovered the value of acceptance tests I was  
really keen on the idea, but to be honest this just isn't a problem I  
come across very often now. By the time my acceptance tests have  
pointed me down to the unit tests / classes I need to work on, I'm  
(especially with a pair to work with) sufficiently focussed to  
remember enough the of collaborations between objects and keep them in  
step with the mocks / stubs. If I do forget something, I get pretty  
rapid feedback when I step back up a level and run the acceptance tests.

I appreciate that the argument 'I can keep all the collaboration in my  
head' is a bit wooly - but equally if the collaborations you're  
mocking are so complex that you can't, then perhaps that in itself is  
a whiff that something is wrong with your design?

Matt Wynne
http://beta.songkick.com
http://blog.mattwynne.net





More information about the rspec-users mailing list