I upgrade spring boot from 1.5.6.RELEASE to 2.2.0.RELEASE, But lead to Mockito @InjectMocks annotation can't work, then I debug, find the order of MockitoTestExecutionListener and SpringBootDependencyInjectionTestExecutionListener is changed. MockitoTestExecutionListener is ahead of SpringBootDependencyInjectionTestExecutionListener, when autowire spring bean with @Autowired, the field which is not inited by spring, Mockito will find the field value is null and throw Exception.

Comment From: wilkinsona

MockitoTestExecutionListener has an order of 2050 and SpringBootDependencyInjectionTestExecutionListener has an order of 2000. The lower the value the higher the precedence so SpringBootDependencyInjectionTestExecutionListener should run before MockitoTestExecutionListener.

If you would like us to spend some more time investigating, can please take the time to create a minimal example that reproduces the problem you have described above? You can share it with us in a separate repository on GitHub or by zipping it up and attaching it to this issue.

Comment From: jacky1193610322

thank you for your reply. MockitoTestExecutionListener has an order 1950 in the code. code source

Comment From: wilkinsona

You're right, sorry. It was set to 2050 when we first ordered it in 2.0, but then changed to 1950 to fix https://github.com/spring-projects/spring-boot/issues/11903. I don't think we can change the order back without regressing that fix.

I'm not sure I fully understand the problem you've described. Can you please provide a small sample that reproduces it?

Comment From: jacky1193610322

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = RepositoryContext.class)
@ActiveProfiles("test")
@Slf4j
public class BigDataServiceImplTest {

    @Autowired
    @InjectMocks
    // which has a field in BigDataServiceImpl
    /*
    @Autowired
    private PosGatewayClient posGatewayClient;
     */
    private BigDataService bigDataService;

    private PosGatewayClient posGatewayClient;

    @Before
    public void init() {
        posGatewayClient = Mockito.spy(new PosGatewayClient(Config.builder()
                .endpoint("endpoint"))
                .appId("appid"))
                .appKey("appkey"))
                .writeTimeoutMilliseconds(3000)
                .build()));
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void redisAlreadyHasTheKey() throws IOException, NoSuchAlgorithmException {
        boolean res = bigDataService.genernateBloomFilterAndUpload();
        assertTrue(res);
    }

}

I want to replace posGatewayClient filed in BigDataServiceImpl which implement the interface BigDataService, but because MockitoTestExecutionListener is ahead of SpringBootDependencyInjectionTestExecutionListener, when initmocks, the field bigDataService is not autowire by spring, it's null, and the method checkNotInterface in mockito class FieldInitializer will fail.

SpringBoot springboot(2.2.0) MockitoTestExecutionListener order is high than SpringBootDependencyInjectionTestExecutionListener, which can't inject field?

Comment From: wilkinsona

Thanks. That wasn't quite what I was looking for, but I think I can see enough now to understand the problem.

Relying on Spring's dependency injection and then modifying its result with Mockito's injection was only ever working accidentally and isn't something that we would recommend. Instead, if you want a field in BigDataServiceImpl to be initialised with a mock or spy, you should get Spring to inject it for you. Relying on Spring's dependency injection and then changing it will pollute the state of the application context. When the test framework caches the context and it's used in a subsequent test, that test will then encounter the spy unexpectedly.

You can either use @SpyBean or you could use a nested @Configuration class to define a PosGatewayClient bean that is a spy.

If you have any further questions, please follow up on Stack Overflow or Gitter. As mentioned in the guidelines for contributing, we prefer to use GitHub issues only for bugs and enhancements.

Comment From: dmak

I have eventually come across the same issue, namely after this commit that reshuffles the listener order, injecting of spies into Spring-autowired beans is not possible out of the box.

Solutions: - Manually set the order of listeners in your test class:

@ExtendWith(SpringExtension.class)
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class, MockitoTestExecutionListener.class, ResetMocksTestExecutionListener.class})
public class MyTest {
}
  • As suggested above by @wilkinsona, move your mock beans into the Spring context and let Spring wire them for you by using @SpyBean, @MockBean or creating them manually (if some specific initialization is needed):
<bean id="restTemplate" class="org.mockito.Mockito" factory-method="mock" c:classToMock="org.springframework.web.client.RestTemplate" />

Relying on Spring's dependency injection and then changing it will pollute the state of the application context.

The process behind @SpyBean actually does the same: at the end of the day it does not matter much who will inject spies into beans (Spring or Mockito). The more important is to control the lifecycle of spies, i.e. reset them after each test case and that is where Spring will be more helpful, however it can be done the same way explicitly in @AfterEach method of the test.