Tuesday, March 12, 2013

Testing Java classes with field injections

Dependency injection frameworks in Java give us 3 points of injection: constructor, field and setter methods. Many of us prefer field injection because we can skip writing setter methods and keep the number of lines to a minimum. The downside of field injection is that the class no longer can be used or tested without a dependency injection framework, for instance during unit-testing. In this post, I'm going to look at how we can unit-test a class with field injections.

Let's take a look at the class we would like to test:

TicketService is the business facade, and the DefaultTicketService is its implementation. It invokes a remote service and maps response DTO objects to domain objects. Given that we want to test the logic in method findTickets, how do we go about this?

Unit testing with Mockito

Using Mockito 1.9.5 (a mocking framework), we can inject mocks to the class being tested using the @InjectMocks annotation.

DefaultTicketService class has two field injections, therefore we declare mocks corresponding to these fields. Then we initialize the DefaultTicketService and annotate it with @InjectMocks. Lastly, we call initMocks in our @Before method, causing the mocks declared to be injected into our DefaultTicketService. Since we have references to our mocks, we can control their behavior in test methods depending on what branch of the code we are testing. First test method tests the normal outcome, whereas the second tests exception handling by configuring the mock to throw an exception.

Testing this class with Mockito is focused and simple as it doesn't require a dependency injection framework and only tests logic in our test class. Alternatively, we could have tested this class using a Spring:

In this case, we have to create a mock implementation of the remote interface and declare it in our test-context.xml. This approach is better suited for integration testing because it will use real beans if corresponding mocks are not declared in the test-context.xml, reducing our control over responses. But, this approach has a bonus, Spring context files are initialized, and therefore tested for consistency.

1 comment:

  1. Additionally, Mockito's Whitebox.setInternalState can be used to set values via reflection.

    ReplyDelete