spring-boot icon indicating copy to clipboard operation
spring-boot copied to clipboard

DeferredLinesWriter registers bean without checking it exists -> prevents me from using multiple MockMvc beans

Open wilx opened this issue 3 years ago • 2 comments

Spring Boot 2.6.9.

I need to use two separate MockMvc beans because we are trying to use Spring REST Docs and it interferes with the request schema which breaks some tests.

I tried instantiating two separate beans in @TestConfiguration:

    @Bean
    @Primary
    public MockMvc mockMvc(final MockMvcBuilder builder, final MockMvcAutoConfiguration configuration) {
        return configuration.mockMvc(builder);
    }

    @Bean
    @Qualifier("mockMvcWithoutRestDocsCustomization")
    public MockMvc mockMvcWithoutRestDocsCustomization(final List<MockMvcBuilderCustomizer> customizers, final MockMvcAutoConfiguration configuration) {
        final List<MockMvcBuilderCustomizer> filteredCustomizers = customizers.stream()
                .filter(cust -> !(cust instanceof AccRestDocsMockMvcBuilderCustomizer))
                .collect(Collectors.toList());
        final DefaultMockMvcBuilder builder = configuration.mockMvcBuilder(filteredCustomizers);
        return configuration.mockMvc(builder);
    }

But I get an exception when the second bean is being instantiated:

Caused by: java.lang.IllegalStateException: Could not register object [org.springframework.boot.test.autoconfigure.web.servlet.SpringBootMockMvcBuilderCustomizer$DeferredLinesWriter@400d6151] under bean name 'org.springframework.boot.test.autoconfigure.web.servlet.SpringBootMockMvcBuilderCustomizer$DeferredLinesWriter': there is already object [org.springframework.boot.test.autoconfigure.web.servlet.SpringBootMockMvcBuilderCustomizer$DeferredLinesWriter@74fabca7] bound
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.registerSingleton(DefaultSingletonBeanRegistry.java:124)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.registerSingleton(DefaultListableBeanFactory.java:1149)
	at org.springframework.boot.test.autoconfigure.web.servlet.SpringBootMockMvcBuilderCustomizer$DeferredLinesWriter.<init>(SpringBootMockMvcBuilderCustomizer.java:233)
	at org.springframework.boot.test.autoconfigure.web.servlet.SpringBootMockMvcBuilderCustomizer.getPrintHandler(SpringBootMockMvcBuilderCustomizer.java:94)
	at org.springframework.boot.test.autoconfigure.web.servlet.SpringBootMockMvcBuilderCustomizer.customize(SpringBootMockMvcBuilderCustomizer.java:82)
	at org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration.mockMvcBuilder(MockMvcAutoConfiguration.java:84)
	at com.ca.apm.acc.configserver.TestContext.mockMvcWithoutRestDocsCustomization(TestContext.java:116)
	at com.ca.apm.acc.configserver.TestContext$$EnhancerBySpringCGLIB$$41d71db4.CGLIB$mockMvcWithoutRestDocsCustomization$4(<generated>)
	at com.ca.apm.acc.configserver.TestContext$$EnhancerBySpringCGLIB$$41d71db4$$FastClassBySpringCGLIB$$6d92f67a.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244)
	at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331)
	at com.ca.apm.acc.configserver.TestContext$$EnhancerBySpringCGLIB$$41d71db4.mockMvcWithoutRestDocsCustomization(<generated>)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
	... 37 more

The problem I see is that SpringBootMockMvcBuilderCustomizer.DeferredLinesWriter is registering itself as bean without checking that it is already registred:

		DeferredLinesWriter(WebApplicationContext context, LinesWriter delegate) {
			Assert.state(context instanceof ConfigurableApplicationContext,
					"A ConfigurableApplicationContext is required for printOnlyOnFailure");
			((ConfigurableApplicationContext) context).getBeanFactory().registerSingleton(BEAN_NAME, this);
			this.delegate = delegate;
		}

Maybe org.springframework.boot.test.autoconfigure.web.servlet.SpringBootMockMvcBuilderCustomizer#getPrintHandler should do some checking before it instantiates DeferredLinesWriter?

wilx avatar Jul 27 '22 09:07 wilx

Thanks for the report.

As described in the docs, other than their class names, we do not consider auto-configuration classes to be public API. As such, injecting MockMvcAutoConfiguration and calling its mockMvc method isn't recommended and isn't something that we really support.

Can we please take a step back and look at the original problem that set you on this path? I'd like to understand this a bit better:

I need to use two separate MockMvc beans because we are trying to use Spring REST Docs and it interferes with the request schema which breaks some tests.

Generally speaking, we recommend that tests that use Spring REST Docs are separate from the application's other tests. This should isolate any REST Docs-specific configuration so that other tests are not affected. Perhaps that's not possible in your case, you prefer to mix the tests, or REST Docs is somehow polluting other tests when it should not be.

To help us to fully understand the underlying problem that you're facing, can you please provide a minimal sample that reproduces it?

wilkinsona avatar Jul 27 '22 10:07 wilkinsona

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

spring-projects-issues avatar Aug 03 '22 10:08 spring-projects-issues

Closing due to lack of requested feedback. If you would like us to look at this issue, please provide the requested information and we will re-open the issue.

spring-projects-issues avatar Aug 10 '22 10:08 spring-projects-issues