Boot version: 2.3.6.RELEASE

Let's say that we have the following production classes:

package a;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class LoanOrdersApplication {

    public static void main(String[] args) {
        SpringApplication.run(LoanOrdersApplication.class, args);
    }

}

A controller

package a.controller;

import a.order.LoanOrder;
import a.order.LoanOrderService;
import reactor.core.publisher.Mono;

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/orders")
public class LoanOrderController {

    private final LoanOrderService loanOrderService;

    public LoanOrderController(LoanOrderService loanOrderService) {
        this.loanOrderService = loanOrderService;
    }

    @GetMapping(path = "/{orderId}")
    Mono<LoanOrder> findOrder(@PathVariable String orderId) {
        return loanOrderService.findOrder(orderId);
    }

}

A service

package a.order;

public interface LoanOrderService {
    Mono<LoanOrder> findOrder(String orderId);
}

and the following test class:

package a;

import org.junit.jupiter.api.Test;

import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class LoanOrdersApplicationTests {

    @Test
    void contextLoads() {
    }

}

According to the documentation (https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-testing-spring-boot-applications-detecting-config) what will happen now is that

When testing Spring Boot applications, this is often not required. Spring Boot's @*Test annotations search for your primary configuration automatically whenever you do not explicitly define one.

and according to the javadocs

If no explicit classes are defined the test will look for nested @Configuration classes, before falling back to a @SpringBootConfiguration search.

Great, I can see in the logs that the proper configuration was found cause I haven't passed the configuration explicitly.

15:13:35.700 [main] DEBUG org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider - Identified candidate component class: file [/home/foo/bar/target/classes/a/LoanOrdersApplication.class]
15:13:35.701 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Found @SpringBootConfiguration a.LoanOrdersApplication for test class a.LoanOrderControllerTest

Let's say that now I want to create a new test, where I want to test only the controller layer with mocked out services. I don't want to use test slices - I want to set up the Boot context without certain autoconfigurations (like Spring Data for example). I will create the controller by myself and also a mock of a service (I'm using @TestConfiguration so that the test that checks the whole context against the LoanOrderApplication doesn't pick my test configuration).

@SpringBootTest(classes = LoanOrderControllerTest.ControllerWithFakeConfig.class)
class LoanOrderControllerTest {

    @Test
    void contextLoads() {
    }

    @TestConfiguration(proxyBeanMethods = false)
    @EnableAutoConfiguration
    static class ControllerWithFakeConfig {

        @Bean
        LoanOrderService testLoanOrderService() {
            return Mockito.mock(LoanOrderService.class);
        }

        @Bean
        LoanOrderController testLoanOrderController(LoanOrderService loanOrderService) {
            return new LoanOrderController(loanOrderService);
        }
    }
}

Running LoanOrderControllerTest makes the build fail because there already is a LoanOrderController.

java.lang.IllegalStateException: Failed to load ApplicationContext
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:132)
    at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:123)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:118)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83)
    at org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener.prepareTestInstance(SpringBootDependencyInjectionTestExecutionListener.java:43)
    at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:244)
    at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:98)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$5(ClassBasedTestDescriptor.java:341)
...
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210)


Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'webHandler' defined in class path resource [org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration$EnableWebFluxConfiguration.class]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'requestMappingHandlerMapping' defined in class path resource [org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration$EnableWebFluxConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'testLoanOrderController' method 
pl.smarttesting.loanorders.controller.LoanOrderController#findOrder(String)
to {GET /orders/{orderId}}: There is already 'loanOrderController' bean method
pl.smarttesting.loanorders.controller.LoanOrderController#findOrder(String) mapped.
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:602)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:897)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:879)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:551)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:750)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:405)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
    at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:120)
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)
    ... 66 more


Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'requestMappingHandlerMapping' defined in class path resource [org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration$EnableWebFluxConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'testLoanOrderController' method 
pl.smarttesting.loanorders.controller.LoanOrderController#findOrder(String)
to {GET /orders/{orderId}}: There is already 'loanOrderController' bean method
pl.smarttesting.loanorders.controller.LoanOrderController#findOrder(String) mapped.
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1794)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:594)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansOfType(DefaultListableBeanFactory.java:624)
    at org.springframework.context.support.AbstractApplicationContext.getBeansOfType(AbstractApplicationContext.java:1251)
    at org.springframework.beans.factory.BeanFactoryUtils.beansOfTypeIncludingAncestors(BeanFactoryUtils.java:378)
    at org.springframework.web.reactive.DispatcherHandler.initStrategies(DispatcherHandler.java:116)
    at org.springframework.web.reactive.DispatcherHandler.setApplicationContext(DispatcherHandler.java:111)
    at org.springframework.context.support.ApplicationContextAwareProcessor.invokeAwareInterfaces(ApplicationContextAwareProcessor.java:123)
    at org.springframework.context.support.ApplicationContextAwareProcessor.postProcessBeforeInitialization(ApplicationContextAwareProcessor.java:100)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:415)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1786)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:594)
    ... 81 more


Caused by: java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'testLoanOrderController' method 
pl.smarttesting.loanorders.controller.LoanOrderController#findOrder(String)
to {GET /orders/{orderId}}: There is already 'loanOrderController' bean method
pl.smarttesting.loanorders.controller.LoanOrderController#findOrder(String) mapped.
    at org.springframework.web.reactive.result.method.AbstractHandlerMethodMapping$MappingRegistry.validateMethodMapping(AbstractHandlerMethodMapping.java:507)
    at org.springframework.web.reactive.result.method.AbstractHandlerMethodMapping$MappingRegistry.register(AbstractHandlerMethodMapping.java:485)
    at org.springframework.web.reactive.result.method.AbstractHandlerMethodMapping.registerHandlerMethod(AbstractHandlerMethodMapping.java:243)
    at org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping.registerHandlerMethod(RequestMappingHandlerMapping.java:272)
    at org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping.registerHandlerMethod(RequestMappingHandlerMapping.java:59)
    at org.springframework.web.reactive.result.method.AbstractHandlerMethodMapping.lambda$detectHandlerMethods$1(AbstractHandlerMethodMapping.java:213)
    at java.base/java.util.LinkedHashMap.forEach(LinkedHashMap.java:723)
    at org.springframework.web.reactive.result.method.AbstractHandlerMethodMapping.detectHandlerMethods(AbstractHandlerMethodMapping.java:211)
    at org.springframework.web.reactive.result.method.AbstractHandlerMethodMapping.initHandlerMethods(AbstractHandlerMethodMapping.java:189)
    at org.springframework.web.reactive.result.method.AbstractHandlerMethodMapping.afterPropertiesSet(AbstractHandlerMethodMapping.java:157)
    at org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping.afterPropertiesSet(RequestMappingHandlerMapping.java:126)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1853)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1790)
    ... 97 more

What I see in test execution logs is that the LoanOrdersApplication class is being resolved as a configuration to be used by the test

15:13:35.700 [main] DEBUG org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider - Identified candidate component class: file [/home/foo/bar/target/classes/a/LoanOrdersApplication.class]
15:13:35.701 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Found @SpringBootConfiguration a.LoanOrdersApplication for test class a.LoanOrderControllerTest

But this is strange cause according to the javadocs

If no explicit classes are defined the test will look for nested @Configuration classes, before falling back to a @SpringBootConfiguration search.

I did pass the explicit Configuration classes via the classes argument so I would assume that my configuration class would be resolved and the main one wouldn't be.

Either I'm doing sth wrong or the documentation & javadocs are wrong or there's a bug somewhere.

cc @snicoll

Comment From: snicoll

TL;DR @SpringBootTest(classes = LoanOrderControllerTest.ControllerWithFakeConfig.class) still detected the @SpringBootConfiguration rather than starting the context with only LoanOrderControllerTest.ControllerWithFakeConfig.class

Comment From: dreis2211

@marcingrzejszczak The javadoc of @TestConfiguration is more specific about this:

/**
 * ....Unlike regular {@code @Configuration} classes the use of
 * {@code @TestConfiguration} does not prevent auto-detection of
 * {@link SpringBootConfiguration @SpringBootConfiguration}.
 */

You can use @Configuration to workaround your issue, I guess. But I agree - the docs on @SpringBootTest#classes are a bit misleading and I stumbled upon this in the past as well.

Comment From: snicoll

@dreis2211 I am confused by the comment. The problem that's raised here is that specifying a classes attribute with a @Configuration does not prevent the auto-detection of @SpringBootConfiguration.

Comment From: dreis2211

@snicoll isn't ControllerWithFakeConfig using @TestConfiguration? @Configuration would actually prevent it.

Comment From: snicoll

@dreis2211 funny. Marcin pasted the wrong example (we were trying several options offline). Marcin can you double check and update the example please?

Comment From: marcingrzejszczak

Actually when I make it @Configuration it works fine. However Stephane AFAIR you said that it didn't work for you.

Comment From: marcingrzejszczak

Ok let's close the issue for now cause it seems that things work fine.