Grails unit vs integration tests

Grails, programming, test

Services

Content here applies to Grails 2.1.3 and may also apply to newer versions.

Setup

We start with three service classes: OneService, TwoService, and ThreeService. OneService uses TwoService which uses ThreeService.

class ThreeService {
def hello() {
"Hello from Three"
}
}

class TwoService {
def threeService
def hello() {
"Hello from Two:" + threeService.hello()
}
}

class OneService {
def twoService
def hello() {
"Hello from One:" + twoService.hello()
}
}

Calling OneService#hello() would normally return "Hello from One:Hello from Two:Hello from Three"

Unit Test

Add a unit test for OneService:

@TestFor(OneService)
class OneServiceTests {
void testSomething() {
defineBeans {
twoService(TwoService)
threeService(ThreeService)
}
service.hello()
}
}

Run it and get this:

| Failure: testSomething(org.van.OneServiceTests)
| java.lang.NullPointerException: Cannot invoke method hello() on null object
at org.van.TwoService.hello(TwoService.groovy:7)
at org.van.OneService.hello(OneService.groovy:7)

The defineBeans{ ... } trick only works for one level, it seems. It got AService#bService hooked up, but BService#cService remained null.

So what happens is that OneService#hello() got to call TwoService#hello() OK. However, when TwoService#hello() tries to call ThreeService#hello(), we get a NPE because TwoService#threeService was not properly injected.

Integration Test

One way to test the flow is to use an integration test instead:

class OneServiceIntTestTests {

@Test
void testSomething() {
OneService oneService = new OneService()
TwoService twoService = new TwoService()
ThreeService threeService = new ThreeService()
twoService.threeService = threeService
oneService.twoService = twoService
oneService.hello()
}
}

While this works, we will need to do the injection plumbing ourselves.

Controllers

Setup

Add a TwoController class:

class TwoController {
def twoService

def index() {
render twoService.hello()
}
}

Accessing the controller returns Hello from Two:Hello from Three as expected.

Unit Test

Add a unit test for TwoController:

@TestFor(TwoController)
class TwoControllerTests {
void testSomething() {
defineBeans {
twoService(TwoService)
threeService(ThreeService)
}
controller.index()
}
}

And it fails similarly to OneServiceTests:

| Failure: testSomething(org.van.TwoControllerTests)
| java.lang.NullPointerException: Cannot invoke method hello() on null object
at org.van.TwoService.hello(TwoService.groovy:7)
at org.van.TwoController.index(TwoController.groovy:7)

Again, the defineBeans { ... } mechanism resolved TwoController#twoService. But once we call into TwoService#hello, it throws an NPE because TwoService#threeService is not properly injected.

Integration Test

And here’s the integration test for TwoController:

class TwoControllerIntTestTests {
@Test
void testSomething() {
TwoController twoController = new TwoController()
TwoService twoService = new TwoService()
ThreeService threeService = new ThreeService()
twoService.threeService = threeService
twoController.twoService = twoService
twoController.index()

assertEquals "Expected output", 'Hello from Two:Hello from Three', twoController.response.text
}
}

Summary

Unit tests, with their defineBeans { ... } mechanism, only helps inject one level of beans for the target (i.e. @TestFor()) class. If the injected beans used by that class then have more injected beans of their own, those are not injected correctly.

This works identically for services and controllers.

Integration tests allow for deeper chaining of injected beans. However, the construction/initialization and the actual injection have to be done manually.

Reference

Grails testing guide