Also on twitter ( twitter.com/nutrun )

From Mocks to Expando

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.

12 Responses to “From Mocks to Expando”

  1. Nat Says:

    That *is* a mock object. Albeit a very simple one that doesn’t report if it hasn’t been called.

  2. George Malamidis Says:

    If we want to be strict with the terminology, you’re right, it is. It carries, however, much less of the weight that comes with traditional Mocks and has far more transparent mechanics, so I’d rather look at it as a Stub on steroids.

  3. Geert Baeyaert Says:

    Yes, but as Nat said, it doesn’t tell you whether any of the expected calls was actually called.

    Doesn’t that worry you?

  4. James Says:

    I think your comparison is a little unfair. It would be better if you wrote out the code for the version of the test using mocks. I’ve had a quick attempt at writing an equivalent test in Ruby using the Mocha mocking library and I would say it compares very favourably with your Expando based one…

    require ‘mocha’
    require ‘test/unit’

    class ForwardResponderTest

  5. George Malamidis Says:

    Geert,

    assert “/location” == location

    If getRequestDispatcher() is not called, this assertion will fail.

    Same goes for the assertions relevant to the forward() method

  6. James Says:

    Seems like my post has been truncated.

    Here’s a link to the example Ruby test using Mocha.

    http://pastie.caboo.se/26540

  7. Jeff Santini Says:

    Hi George,

    I am very much in favor of your “crusade” against the bad things people can do with mock libraries. In relation to Nat’s comment above and your reply that your expando object is a mock, would I be right in saying people who compain about mocks(myself included) do so pretty much because of their misuse, not because they serve no purpose?

    The paper at http://www.jmock.org/oopsla2004.pdf lays out the case for mocks. Is there anything in there to disagree with?

  8. Jeff Santini Says:

    Actually the paper is not that short, sections 4 “Mock Objects in Practice” and 5 “Misconceptions about Mocks” are the bits more related to how Mocks can go wrong and what is the pro-Mock answer to these problems.

  9. Geert Baeyaert Says:

    This is puzzling.

    As far as I understand, if getRequestDispatcher is not called, the code in the closure won’t be executed, including any asserts that might be there.

    What am I not seeing?

  10. George Malamidis Says:

    Geert,

    Yes, you’re right. I’ve ammended the test to reflect your and Nat’s observation. Thanks for pointing this out.

  11. Jesse O'Neill-Oine Says:

    This solution just gives you that “yeah, that’s how that should work” feeling. This same pattern would work great in JavaScript too, which is nice in a Web 2.1415 world. Stub on steroids seems like the perfect description.

    Thanks!

  12. Ben Butler-Cole Says:

    Hi George

    Expando appears to be a great way of knocking up a stub, but a poor substitute for a mock. The problem is that there is no point in the test where you clearly say “the ForwardResponder should forward the response using the dispatcher”.

    The alternative with mocks (JMock here) reads much better to me:

    dispatcher.expects(once())
    .method(“forward”)
    .with(eq(request), eq(response));

    Rather than splitting the statement across a local variable definition, an assignment and an assertion, it’s all there in a declarative and fairly readable (modulo Java syntax) way.

    I agree with you that mocks returning mocks leave a nasty taste in the mouth. Here I would use a stub (by all means a glossy Expando stub if you have such a thing to hand) for the request which returns the dispatcher mock.

    And I would leave assertions about the location passed to the request for another test. Indeed I would extend the “one assert per test” guideline to “one mock per test”.

    Ben