Saturday, November 24, 2007

RFC: A mock too far?

I love using mocks as it makes it possible to write unit tests for small parts of a bigger system without depending on it. Using mocks is very simple, just create a mock of an interface (or class, schhhh) , set up the expectations on it, and then execute the test. When using mocks it is easy to get 100% coverage without relying on others. But I have come to the conclusion that this can be taken too far, way too far.

I have tests that do more setting up the mock expectations than actually testing the code. This makes the tests much harder to read, and I'm having problems going back to 6 months old code and understand what a test actually tests. And in my eyes this is a big problem because unit test code should be as simple as possible.

So what I'm interested in is how should I do mock testing, without drowning in mocking expectations. Does anyone have any ideas on how to do it? Have I gone a mock too far?

As a simple example I will show my unit tests of my Clear case implementation for the Hudson CI server. (But my production code looks very similar unfortunately).

I've selected to mock out the clear tool command execution in the test, ie I dont want to issue real commands using the clear case command line tool (and I don't have a Clear case server or client at home). In the below example I want to verify that when the plugin polls the CC server for changes, then it should issue a "lshistory" method call. But to get it to the point where I can run the tests I have to mock out the Hudson dependencies, ie get the latest build, check the time stamp of it, mock a list of changes that the "lshistory" should return.


@Test
public void testPollChanges() throws Exception {
final ArrayList list = new ArrayList();
list.add(new String[] { "A" });
final Calendar mockedCalendar = Calendar.getInstance();
mockedCalendar.setTimeInMillis(400000);

context.checking(new Expectations() {
{
one(clearTool).lshistory(with(any(ClearToolLauncher.class)),
with(equal(mockedCalendar.getTime())),
with(equal("viewname")), with(equal("branch")));
will(returnValue(list));
one(clearTool).setVobPaths(with(equal("vob")));
}
});
classContext.checking(new Expectations() {
{
one(build).getTimestamp();
will(returnValue(mockedCalendar));
one(project).getLastBuild();
will(returnValue(build));
}
});

ClearCaseSCM scm = new ClearCaseSCM(clearTool, "branch",
"configspec", "viewname", true, "vob", false, "");
boolean hasChanges = scm.pollChanges(project, launcher,
workspace, taskListener);
assertTrue("The first time should always return true", hasChanges);

classContext.assertIsSatisfied();
context.assertIsSatisfied();
}
As you can see the mock expectations take up at least two thirds of the test. Of course I can refactor the tests by adding helper methods that will set up the expections, but many times the expectations are similar but not equal.

12 comments:

Nat Pryce said...

You're mocking things that are not worth mocking. You should try to mock only interfaces used to tell objects to do things.

E.g.

* don't mock values -- just use the real thing and ensure values are immutable

* don't mock (or even write in the first place) objects that are just a bag of stuff with only getters and setters in the API. Write objects that do something, not merely store stuff

* don't use "one" expectations for getters.

* if an object is just a bunch of getters (or methods that don't start with get but do just return stuff without effecting a state change in the implementor) don't mock it, just write a stub for testing.

time4tea said...

coupla things.
- testPollChanges isnt a great name for a test. How about testPollsClearCaseServerForChangesAndGetStuff
(as you dont do anything with the returned values)

- you dont really need to use the context thing at all, if you're using junit3 or 4. this means you dont need to assertIsSatisfied.

- you've got two contexts. not required at all.

- list could be inlined as Arrays.asList("A")

- the value of the calendar isn't important, so you don't really need to set it.

- although really the main problem is that the test doesn't tet any behaviour, it just tests sone delegated call.

- so really its all about how you write the tests mocks allow you to write bad stuff, same as anything.

greyfairer said...

Hi there,

I think you strategy is not too bad.
If you want to keep it a 'Unit' test and not an 'Integration' test, you should in theory mock any dependencies on the Hudson Model. However, if the objects you mock are really plain value objects, I wouldn't mock them either.

You shouldn't limit yourself to using one() as invocation count, especially for getters. Your class under test should be allowed to call getters zero or multiple times, I guess, so you'd better do allowing(foo).getBar();will(returnValue(baz));

Also, a shortcut to return different values on consecutive calls, you could do exactly(2).of(foo).findBar();
will(onConsecutiveCalls(
returnValue(baz1),returnValue(baz2));

pembe maske said...

orjin krem
alışveriş
formula 21 formen
formula 21 forman
supratall
gainmax
dermana krem
hcg damla
gastromid
hemoriva
epilactiva
tutune son
power balance
biktim tozu
bitkisel urunler
manxl
gainmax
dermana
parfum
devall krem
formula 21
romadur
v-boom
vboom
altın çilek form seti
power ixir
moliva
nicdur
zayiflama kupesi
my slimmer
orjin krem
ömer çoskun

Sinus headache said...

Your post is really good Home remedies for diarrhea Cure ringworm naturally Home remedies for mouth ulcers Prevent dizziness White spots on skin Stomach gas Hard stool treatment Health benefits of almonds Cure ear infection Under eye wrinkles Natural laxative foods Baby ear infection Common digestive system diseases and disorders Get pregnant easily Home remedies for pneumonia Health benefits of papaya Home remedies for common fever Home remedies for cough Home remedies for boils Home remedies for burns Ear wax removal Home remedies for anorexia Home remedies for bursitis Smooth and glowing skin Heartburn relief

saim said...

I have read your blog and find that your articles are amazing; I have added this into


find doctor list

Viagra Online without prescription said...

I think that this is a great way to taste it , excellent idea! this is one of the best posts that I have ever seen; you may include some more ideas in the same theme.

saim said...

Nice work! Your post is an excellent example of why I keep coming back to read your excellent quality content that is forever updated.

natural minerals
best naturals
buy curcumin

0Zero7 said...

The blog is written in such a way that it is so easy to read and understand.
web hosting pakistan

Tasly Hakkında said...

My Slimmer
Complex 41
Formula 21 Formen
Epilamor
Formula 21 Formen
Gece Gözlüğü
moliva
afrika mangosu
formula 21
panax
trim fast
models
clothing fashion
lingerie
aquarium
kadın sağlık bilgisi
sağlık bilgileri
magazin
dini bilgiler
bitkiler
diyet
oto haber
yemek tarifleri
yemek tarifleri

Coach Outlet Store Online said...

So nice post. I will talk it with my friends.

sinta dewi said...

Prediksi Bola Arsenal vs Chelsea 26 April 2015 | waletbet.net
http://167.114.137.222/~waletnet/prediksi-bola/prediksi-skor-arsenal-vs-chelsea-26-april-2015/