Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 4.0

Groovy's 'as' operator can be used with closures in a neat way which is great for developer testing in simple scenarios. We haven't found this technique to be so powerful that we want to do away with dynamic mocking, but it can be very useful in simple cases none-the-less.

Suppose we are using Interface Oriented Design and as sometimes advocated we have defined a number of short interfaces as per below. (Note: we ignore the discussion about whether interfaces are as valuable a design approach when using dynamic languages that support duck-typing.)

Code Block
interface Logger { def log(message) }
interface Helper { def doSomething(param) }
interface Factory { Helper getInstance() }

Now, using a coding style typically used with dependency injection (as you might use with Spring), we might code up an application class as follows:

Code Block
class MyApp {
    private factory
    private logger
    MyApp(Factory factory, Logger logger) {
        this.logger = logger
        this.factory = factory
    }
    def doMyLogic(param) {
        factory.getInstance().doSomething(param)
        logger.log('Something done with: ' + param)
    }
}

To testing this, we could use Groovy's built-in mocking or some other Java-based dynamic mocking framework. Alternatively, we could write our own static mocks. But no one does that these days I hear you say! Well, they never had the ease of using closures, which bring dynamic power to static mocks, so here we go:

Code Block
def param = 'DUMMY STRING'
def logger = { message -> assert message == 'Something done with: ' + param}
def helper = { assert it == param }
def factory = { helper as Helper }
def myApp = new MyApp(factory as Factory, logger as Logger)
myApp.doMyLogic(param)

That was easy. Behind the scenes, Groovy creates a proxy object for us that implements the interface and is backed by the closure.

Easy yes, however, the technique as described above assumes our interfaces all have one method. What about more complex examples? Well, the 'as' method works with Maps of closures too. Suppose our helper interface was defined as follows:

Code Block
interface Helper {
    def doSomething(param)
    def doSomethingElse(param)
}

And our application modified to use both methods:

Code Block
...
    def doMyLogic(param) {
        def helper = factory.getInstance()
        helper.doSomething(param)
        helper.doSomethingElse(param)
        logger.log('Something done with: ' + param)
    }
...

We simply use a map of closures with the key used being the same name as the methods of the interface, like so:

Code Block
...
def helperMethod = { assert it == param }
def helper = [doSomething:helperMethod, doSomethingElse:helperMethod]
// as before
def factory = { helper as Helper }
...

Still easy!

For this simple example, where we wanted each method to be the same (i.e. implementing the same code) we could have done away with the map altogether, e.g. the following would work, making each method be backed by the closure:

Code Block
def factory = { helperMethod as Helper }

More Information

See also: