I use Spring Boot 3.4.3. If I replace the deprecated @SpyBean annotation with @MockitoSpyBean, the associated test fails. This can be seen in the following example from https://www.baeldung.com/spring-testing-scheduled-annotation:
package de.test;
import org.junit.jupiter.api.Test;
import org.mockito.Answers;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.assertj.core.api.Assertions.assertThat;
@SpringJUnitConfig(ScheduledConfig.class)
class ScheduledIntegrationTest {
@SpyBean //Test works fine
//@MockitoSpyBean //Test failed (Unable to select a bean to wrap: there are no beans of type de.test.Counter)
//@MockitoBean(answers = Answers.CALLS_REAL_METHODS) //Test failed (Expecting actual: 0 to be greater than: 0)
Counter counter;
@Test
public void givenSleepBy100ms_whenGetInvocationCount_thenIsGreaterThanZero()
throws InterruptedException {
Thread.sleep(100L);
assertThat(counter.getCount()).isGreaterThan(0);
}
}
@Configuration
@EnableScheduling
class ScheduledConfig {
}
@Component
class Counter {
private int count = 0;
@Scheduled(fixedDelay = 50)
public void scheduled() {
this.count++;
System.out.println("Value of Count: " + count);
}
public int getCount() {
return this.count;
}
}
Result with @SpyBean 17:56:43.661 [Test worker] INFO org.springframework.scheduling.config.TaskSchedulerRouter -- No TaskScheduler/ScheduledExecutorService bean found for scheduled processing Value of Count: 1 Value of Count: 2 Value of Count: 3 Value of Count: 4
Task :test
Result with @MockitoSpyBean Unable to select a bean to wrap: there are no beans of type de.test.Counter (as required by field 'ScheduledIntegrationTest.counter'). If the bean is defined in a @Bean method, make sure the return type is the most specific type possible (for example, the concrete implementation type). java.lang.IllegalStateException: Unable to select a bean to wrap: there are no beans of type de.test.Counter (as required by field 'ScheduledIntegrationTest.counter'). If the bean is defined in a @Bean method, make sure the return type is the most specific type possible (for example, the concrete implementation type).
Result with @MockitoBean(answers = Answers.CALLS_REAL_METHODS) Expecting actual: 0 to be greater than: 0
Is this a bug or have I missed something?
Comment From: tobias-lippert
Hi,
The MockitoSpyBean
behavior you observe, is definitely intended. MockitoSpyBean
uses the wrap strategy which requires the original bean to exist in the context, see https://docs.spring.io/spring-framework/reference/testing/testcontext-framework/bean-overriding.html#testcontext-bean-overriding-strategy. With your provided code, the context doesn't contain a Counter
bean. If you create a Counter
bean in your configuration class, the test passes using MockitoSpyBean
.
I'm not part of the Spring core team, so I'm not sure about the desired behavior of MockitoBean
. I'm quite surprised that it should support calling real methods as I think that's a use case for MockitoSpyBean
. MockitoBean
never uses the wrap strategy, so I'm not sure if this can even work.