@MockBean document states

Any existing single bean of the same type defined in the context will be replaced by the mock.

However, it also replaces the bean for a sub-type. I suspect it's because the following line as ListableBeanFactory#getBeanNamesForType also includes subclasses.

https://github.com/spring-projects/spring-boot/blob/303dd4cfe0471f8f7558edeed101059bd67d3346/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoPostProcessor.java#L250-L251

I've updated MockBeanOnTestClassForExistingBeanIntegrationTests to demonstrate what I expected, where FailingExampleService is a subclass of ExampleService (Please ignore potential wiring issues):

@ExtendWith(SpringExtension.class)
@MockBean(ExampleService.class)
class MockBeanOnTestClassForExistingBeanIntegrationTests {
    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private ExampleServiceCaller caller;

    @Test
    void testMocking() {
        given(this.caller.getService().greeting()).willReturn("Boot");
        assertThat(this.caller.sayGreeting()).isEqualTo("I say Boot");
    }

    @Test
    void testBeans() {
        assertThat(applicationContext.getBeanNamesForType(ExampleService.class)).hasSize(2);
        assertThat(applicationContext.getBeanNamesForType(FailingExampleService.class)).hasSize(1);
    }

    @Configuration(proxyBeanMethods = false)
    @Import({ ExampleServiceCaller.class, FailingExampleService.class })
    static class Config {

    }

}

Spring Boot version: 2.5.5

Comment From: wilkinsona

Thanks for the report. Unfortunately, I'm not sure that we can change this without potentially breaking people's existing tests. I'm also not sure that we should as, without specifically defining what is meant by "same", I don't think it's unreasonable for a sub-class to be replaced.

For 2.x, we can clarify the javadoc to remove the confusion by stating that the type matching includes sub-classes. We could then consider a behaviour change in 3.x although that may require a Framework change as there is no equivalent of getBeanNamesForType that does not match sub-classes.

Comment From: wilkinsona

Flagging for a team meeting so that we can discuss changing the matching behaviour in 3.0 and open an issue to track the change if necessary.

Comment From: philwebb

We're going to update the documentation to indicate that matching also includes sub-classes. For an exact single bean match we're going to suggest that name is used.

Comment From: hisener

What's the decision for 3.0? 👀

Comment From: wilkinsona

We aren't going to change the behaviour.