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.