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.