DeferredLinesWriter registers bean without checking it exists -> prevents me from using multiple MockMvc beans
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?
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?
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.
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.