[Esug-list] Proposal for Mock Objects at ESUG 2011

Colin Putney colin at wiresong.com
Fri May 27 17:11:57 EDT 2011


On Fri, May 27, 2011 at 12:42 PM, Frank Shearar <frank.shearar at gmail.com> wrote:
> You miss the point of mocks. Mocks are there to allow you to test your
> code quickly and cheaply against things that may not work in a quick
> manner.

Not likely. Hernán has been around the block and he speaks from long
experience and deep thinking on software design. If you read his post
a little more carefully, you'll see that he distinguishes between test
doubles in general and mocks as a particular form of test double. He
uses test doubles - you don't get 23,000 tests to run in 7 minutes
without them. It's just *mocks* that are mostly unnecessary.

Personally, I find that mocks are *very* useful in a few very specific
situations, but they can cause problems it other situations. They're
like a very specialized tool - invaluable when you need it, but kept
in the bottom of the toolbox and not used very often. Where I find
them useful is in testing communication. For example, I'll use a
MockWritestream to throw an exception if the code under test writes
something unexpected into the stream. It's handy when testing code
that writes out files in a particular format, or communicates with
remote systems over the network. It might also be useful to test
communications across formal module boundaries, where it's
particularly important that a specific protocol be used - eg, for
something like Prevalayer.

My favourite testing pattern is TrivialImplementation. Instead of
using a test double or an expensive "real" object, I often create a
cheap/trivial implementation of the objects that collaborate with the
objects I'm testing. For example, Monticello2 has several repository
classes. There are implementations that store code in a single file,
in a directory of files, on a remote machine via sockets, on a web
server via HTTP etc. There's also a trivial implementation that just
uses an in-memory Dictionary for storage. It's a complete
implementation of the repository protocol, and Monticello can use it
"for real", but it's not as robust as the other kinds of repository.
All the repository implementations, including MemoryRepository, have a
suite of tests that ensure that they work correctly. But when I'm
testing other parts of Monticello that interact with a repository,
those tests use a MemoryRepository.

I know that BDD folks like to talk about testing behaviour rather than
state, but I don't find the distinction useful. Testing state breaks
the encapsulation of the objects under test, and couples the test too
closely to their internal implementation. Testing behaviour using
mocks *does the same thing*; it just restricts the implementation in a
different way. I find it's better to give the implementation a degree
of freedom by testing "results". Figuring out what result you're
looking for can be difficult - it requires thinking about what a
passing test really tells you.

Here's an example - let's say we we're testing an implementation of
Set. Here's the version that tests state:

| set |
set := Set new.
set add: 3.
self assert: (set instVarNamed: 'array') = #(nil nil nil 3 nil)

Here we test behaviour:

| set |
mock := MockArray new: 5
mock expect: (Message selector: #at:put: arguments: #(4 3)).
set := Set withArray: mock.
set add: 3.

I prefer this approach. It gives the implementation a lot of freedom,
while ensuring that it does what we really want:

| set |
set := Set new.
set add: 3.
self assert: (set includes: 3).

In short, I agree with Hernán. Mocks can be useful, but they're often
overused. In most cases, they're unnecessary.

Colin




More information about the Esug-list mailing list