24 April 2007
Test Smell: Mocking concrete classes
One approach to Interaction Testing is to mock concrete classes rather than interfaces. The technique is to inherit from the class you want to mock and override the methods that will be called within the test, either manually or with any of the mocking frameworks. I think it's a technique that should be used only when you really have no other options.
Here's an example of mocking by hand, the test verifies that the music centre starts the CD player at the requested time. Assume that setting the schedule on a CdPlayer object involves triggering some behaviour we don't want in the test, so we override the scheduleToStartAt and verify afterwards that we've called it with the right argument.
public class MusicCentreTest {
@Test public void startsCdPlayerAtTimeRequested() {
final MutableTime scheduledTime = new MutableTime();
CdPlayer player = new CdPlayer() {
@Override
public void scheduleToStartAt(Time startTime) {
scheduledTime.set(startTime);
}
}
MusicCentre centre = new MusicCentre(player);
centre.startMediaAt(LATER);
assertEquals(LATER, scheduledTime.get());
}
}
The problem with this approach is that it leaves the relationship between the objects implicit. I hope we've made clear by now that the intention of Test-Driven Development with Mock Objects is to discover relationships between objects. If I subclass, there's nothing in the domain code to make such a relationship visible, just methods on an object. This makes it harder to see if the service that supports this relationship might be relevant elsewhere and I'll have to do the analysis again next time I work with the class. To make the point, here's a possible implementation of CdPlayer:
public class CdPlayer {
public void scheduleToStartAt(Time startTime) { …
public void stop() { …
public void gotoTrack(int trackNumber) { …
public void spinUpDisk() { …
public void eject() { …
}
It turns out that my MusicCentre object only uses the starting and stopping methods on the CdPlayer, the rest are used by some other part of the system. I'm over-specifying my MediaCentre by requiring it to talk to a CdPlayer, what it actually needs is a ScheduledDevice. Robert Martin made the point (back in 1996) in his Interface Segregation Principle that "Clients should not be forced to depend upon interfaces that they do not use", but that's exactly what we do when we mock a concrete class.
There's a more subtle but powerful reason for not mocking concrete classes. As part of the TDD with Mocks process, I have to think up names for the relationships I discover—in this example the ScheduledDevice. I find that this makes me think harder about the domain and teases out concepts that I might otherwise miss. Once something has a name, I can talk about it.
In case of emergency
There are a few occasions when I have to put up with this smell. The least unacceptable situation is where I'm working with legacy code that I control but can't change all at once. Alternatively, I might be working with third party code that I can't change. As I wrote before, it's usually better to write a veneer over an external library rather than mock it directly, but sometimes it's just too hard. Either way, these are unfortunate but necessary compromises that I would try to work my way out of as soon as possible. The longer I leave them in the code, the more likely it is that some brittleness in the design will cause me grief.
Above all, do not mock by overriding a class's internal features, which just locks down your test to the quirks of the current implementation. Override only visible methods. This rule also prohibits exposing internal methods just so you can override them in a test. If you can't get to the structure you need, then the tests are telling you that it's time to break up the class into smaller, composable features.
Labels: design, listening to the tests, testability
I hope you get my point, my english isn't very good. What I'd like to know is how to apply this approach when there's several instances of each class/interface which state matters and that likely shall be persisted/depersisted.
So, what am I testing here? If I'm testing persistence, then I'd create a graph of objects, save into a DB, reload, and compare. If I'm testing behaviour then I want just the objects that are relevant to the test. If these are value objects, then there's probably nothing to mock, just use real ones. If there's behaviour then I might have collaborators which would need an interface.
Hmmm. We need more examples.
In this situation I would mock the Customer, but I don't see any value in using an interface that will be exact to the object itself, instead of mocking the object directly. I tend to think that interfaces are appropiate for services but not for entities. [when I talk about services or entities, i'm refering to the domain-driven design concepts]
Thank you for your answers.
What happens when the order is processed? Does the state of the Customer change?
I'll try to find better, real-world, examples.
Thank you again, Steve
I'm also interested in how to use interaction testing as a design approach specifically for a (DDD) domain model.
In addition to Lusimi's example the one from the BDD site:
http://behaviour-driven.org/BehaviourDrivenProgrammingExample
This shows (in DDD terms) a Service being tested and its dependencies being mocked.
Thats fine (though it only scratches the surface), and I'd probably test the service that way (probably with additional state tests).
However if we're using the tests as a design technique would we next define interface(s) for the roles the Account is playing here?
Links to this post:
<< Home
© The authors

