Developer Testing using Maps and Expandos instead of Mocks

Suppose we are trying to test the following application:

class MyApp {
    def factory
    def logger
    def doBusinessLogic(param) {
        def myObj = factory.instance
        myObj.doSomething(param)
        myObj.doSomethingElse(param)
        logger.log('Something done with: ' + param)
    }
}

We might be tempted to replace logger and factory with Groovy's built-in mocks, but it turns out that Maps or Expandos are often sufficiently powerful enough in Groovy that we don't need full blown dynamic mocks for this example.

Instead of a mock for logger, we can use an Expando as follows:

...
    def logger = new Expando()
    logger.log = { msg -> assert msg == 'Something done with: ' + param }
...

Here the expected behaviour for our production code is captured within a Closure. (When using TDD, this closure would force the production code we saw in our original code to be created.)

Instead of a mock for factory, we can use a simple map as follows:

...
    def factory = [instance: businessObj]
...

Here, businessObj is the object we want the factory to return, though in general this could be a Closure similar to what we did for the Expando above.

Putting this altogether yields (after some refactoring) the following complete test:

class MyAppTest extends GroovyTestCase {
    void testDoesBusinessLogic() {
        // triangulate
        checkDoesBusinessLogic "case1"
        checkDoesBusinessLogic "case2"
    }
    private checkDoesBusinessLogic(param) {
        def logger = setUpLoggingExpectations(param)
        def businessObj = setUpBusinessObjectExpectations(param)
        def factory = [instance: businessObj]
        def cut = new MyApp(logger:logger, factory:factory)
        cut.doBusinessLogic(param)
    }
    private setUpLoggingExpectations(param) {
        def shouldProduceCorrectLogMessage =
            { msg -> assert msg == 'Something done with: ' + param }
        def logger = new Expando()
        logger.log = shouldProduceCorrectLogMessage
        return logger
    }
    private setUpBusinessObjectExpectations(param) {
        def shouldBeCalledWithInputParam = { assert it == param }
        def myObj = new Expando()
        myObj.doSomething = shouldBeCalledWithInputParam
        myObj.doSomethingElse = shouldBeCalledWithInputParam
        return myObj
    }
}

See also: Developer Testing using Closures instead of Mocks

Labels

 
(None)
  1. Jun 05, 2007

    Paul King says:

    (?) The consequence is that you can't use types for the declaration of the mocke...

    The consequence is that you can't use types for the declaration of the mocked fields in MyApp, isn't it?

    Correct. The returned type will be Expando or Map which both work fine if you are using duck typing (defined with def) but not if you want stronger typing. For that see Developer Testing using Closures instead of Mocks. This would mean you change this line:

    def factory = [instance: businessObj]

    to:

    def factory = { businessObj } as Factory

    where factory is some interface you have defined:

    interface Factory {
        def getInstance()
    }

    If you scenario is more complex than this, consider using proper Groovy mocks.