Revised example test with added assertions to verify methods are being called. Thanks to Nat and Geert Baeyaert for spotting this.
Up until recently (a few hours ago, that is…), I lived with the notion that Mock Objects are a necessary evil. You don’t exactly like them, but you can’t really do without them. They have obvious advantages, but something about them never felt quite right.
So I spent my little recent while attempting to figure out the most likely among a number of potential candidates for the Mock Object API of my liking in Groovy, and, believe you me, there are plenty of options. The answer – with the help from a bunch of nice people on the Groovy dev mailing list:
No Mocks.
The technique does not have to be Groovy specific, I’m pretty sure it can be achieved with most non-statically typed languages that support closures and the ability to dynamically attach behavior (methods) to Objects.
Groovy’ s (relatively) secret weapon for the task is the Expando class. Oversimplified, instances of Expando are expandable Objects to which you can add methods in the form of closures.
Here’s an example of my mock-less unit test:
package aerie.response
class ForwardResponderTest extends GroovyTestCase {
void testShouldForwardResponse() {
def getRequestDispatcherCalled = false
def forwardCalled = false
def dispatcher = new Expando()
def request = new Expando()
def response = new Object()
request.getRequestDispatcher = { location ->
assert "/location" == location
getRequestDispatcherCalled = true
dispatcher
}
dispatcher.forward = { req, resp ->
assert request == req
assert response == resp
forwardCalled = true
}
new ForwardResponder(request, response, "/location").respond()
assert getRequestDispatcherCalled
assert forwardCalled
}
}
… for the following class:
package aerie.response
class ForwardResponder implements Responder {
def request
def response
def location
def ForwardResponder(request, response, location) {
this.request = request
this.response = response
this.location = location
}
def respond() {
request.getRequestDispatcher(location).forward(request, response)
}
}
It’s worth noting that here we’re dealing with testing contents of the javax.servlet API, one of the most notorious in terms of its un-testability packages in Java.
For starters, if we considered the test in question in traditional Mock Object terms, we’d find ourselves in a situation where we’d have to write a Mock for HttpServletRequest on which we would expect a call to getRequestDispatcher(String location), which would in turn return another Mock – RequestDispatcher, on which we’d expect forward(HttpServletRequest request, HttpServletResponse response) to be called. In “I like my code compact and concise and the intent of my tests clear” terms: insane.
What Expando does for us here is Stubs with an attitude. Done right.
This test is all down to language level – assert is a Java keyword. We’re nowhere near importing a Mock library or having to understand the mechanics of a paradigm that is not native to the core language. We are asserting on what and only what we want to be asserting on. The class under test is tested completely in isolation and there is zero clutter in the form of stubbed interface implementations of the shunt collaborators that interact with it.
Of course none of these would be possible if the language, or design concerns, disallow or discourage duck typing. But even if we’re worried about whatever problems might arise from the lack of statically typed parameters or strongly typed objects, I would suggest that the absence of it is quite OK and manageable at package level where everything is under our control and outsiders cannot really go too creative – as in dangerous – with it.