Search This Blog

Tuesday, March 7, 2017

Take care when using @Injectmocks

Well @Injectmocks is nice and evil in one tag. Assume we have this service 

@Service
public class SampleService {
    private final Logger LOG = Logger.getLogger(SampleService.class.getName());

    @Autowired
    private SampleDependency1   dependency1;

    public Long sampleMethod() {
        LOG.info("Calling sampleMethod");
        Long l = dependency1.calculateValue();
        LOG.info("l = " + l);
        return  l;
    }
}
and the corresponding test
@RunWith(SpringRunner.class)
@SpringBootTest
public class InjectmocksApplicationTests {
    @Mock
    private SampleDependency1 dependency1;

    @InjectMocks
    private SampleService service = new SampleService();

    @Test
    public void contextLoads() {
        when(dependency1.calculateValue()).thenReturn(null);
        final Long l = service.sampleMethod();
        Assert.isNull(l, "well l should be null");
    }
}

Ok, should work and will call our service with the injected mock for dependency1. Now lets add a second dependency like this:
@Service
public class SampleService {
    private final Logger LOG = Logger.getLogger(SampleService.class.getName());

    @Autowired
    private SampleDependency1   dependency1;

    @Autowired
    private SampleDependency2   dependency2;

    public Long sampleMethod() {
        LOG.info("Calling sampleMethod");
        Long l = dependency1.calculateValue();
        l= dependency2.calculateValue();
        LOG.info("l = " + l);
        return  l;
    }
This will compile, but running your test will result in 
java.lang.NullPointerException at net.kambrium.example.SampleService.sampleMethod(SampleService.java:23)
because you forgot to add dependency2 to your test class. To avoid this do
1. Use constructor wiring for your dependencies
2. Use @InjectMocks wisely and go searching on your tests for usage of the service you change and adjust the test cases
I personally prefer 1. as it always starts complaining at compile time. If your are using checkstyle you will see this warning, when using field injection:
Spring Team recommends: "Always use constructor based dependency injection in your beans. Always use assertions for mandatory dependencies"