Indeed, I tried very hard to word that very carefully. I’m often reminded of https://xkcd.com/1053/. I tend to benefit from the occasional reminder that not everyone shares the same context.
Indeed, Mockito (and JMock and NSubstitute) provide a thing that I label as a “dynamic test double”. I mean an object that implements an interface without being a literal compile-time class that implements that interface. In Java, we build them with Dynamic Method Invocation Handlers. In Ruby we can use send()
. These test double libraries allow us to implement an interface without writing a class to do that. This is unlike in C++ where we typically use templates to generate a static test double.
Without the technical details, by “dynamic test double” here I mean to intercept individual method calls for the purpose of simulating them (dynamic test double) as opposed to writing an alternative (usually lightweight) implementation of the entire interface (static test double).
The simplest example is an anonymous implementation, such as we can do in Java:
new Catalog() {
Option<int> findPrice(String barcode) {
return Option.of(750);
}
}
That’s a static test double which, most would argue, is simpler than creating a dynamic test double with Mockito and saying “when findPrice(with(anything)))
, return Option.of(750)
”. My point was this: even if some dynamic test doubles are more complicated (to use) than some simpler alternatives, I find value in having a uniform syntax for my test doubles that compensates for the extra complication. Moreover, I can read those dynamic test doubles quite easily, so the extra complication doesn’t much hurt me anyway. Not every programmer will feel the same way about that.
Back to Test Doubles and Contract Tests…
I use test doubles for Collaboration Tests. Those test doubles specify parts of the contract of the next layer. They collectively “add up to” the contract of the next layer.
Test doubles also “implement” the contract purely abstractly. There is no behavior to check. They only detect events and return hardcoded values. There’s nothing inside them to check. Mockito works. NSubstitute works. Dynamic method interception works.
If we wrote Contract Tests for the Test Doubles, then we’d be doing double-entry bookkeeping. We’d just write the same thing twice in order to increase the chances of noticing a mistake. This might have value, but I typically judge it too little to be worth the effort.
If I need a Lightweight Implementation (for various reasons), then I might want to check it with the Contract Tests for its interface. This would happen if the implementation needed to be complicated because it implements a complicated external interface, such as HTTP, SMTP, or java.sql.ResultSet. I would try to make this lightweight implementation so lightweight that it was obviously correct. I would usually prefer to use 6 different lightweight implementations of different parts of a big interface over combining them into one big implementation that tries to be all things for all people. The smaller implementations will probably be obviously correct in a way that the big one wouldn’t be.
And, of course, this is just a preference and not a rule.
How much does that help? Did I get close?
You’re welcome. Indeed, I believe I understood you easily. (You tell me!) As for the energy it takes to understand where someone is coming from, I find that work interesting and enjoyable. And it pays well from time to time.