Description
Proposal
spring boot version: 3.3.7
spring boot test version: 3.3.7
wiremock-spring-boot version: 3.3.0 (tried all newer and older versions too)
Currently mocking one service we call multiple times across different integration tests on the same port.
Once one of the integration tests introduces @SpyBean, there is a change in wiremock behavior in that the wiremock server is not stopped once all the test methods have been executed in the class. Currently this results in a port conflict with the next integration test class which tries to start up wiremock on the same port:
java.lang.IllegalStateException: Failed to load ApplicationContext for [WebMergedContextConfiguration@1efc6a80 testClass = com.uk.IntegrationTestTwo, locations = [], classes = [com.uk.Application], contextInitializerClasses = [], activeProfiles = [], propertySourceDescriptors = [], propertySourceProperties = ["org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true"], contextCustomizers = [org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@7776ab, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@2f1de2d6, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@1941a8ff, org.springframework.boot.test.web.reactor.netty.DisableReactorResourceFactoryGlobalResourcesContextCustomizerFactory$DisableReactorResourceFactoryGlobalResourcesContextCustomizerCustomizer@2250b9f2, org.springframework.boot.test.autoconfigure.OnFailureConditionReportContextCustomizerFactory$OnFailureConditionReportContextCustomizer@142269f2, org.springframework.boot.test.autoconfigure.actuate.observability.ObservabilityContextCustomizerFactory$DisableObservabilityContextCustomizer@1f, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizer@6986bbaf, org.wiremock.spring.internal.WireMockContextCustomizer@c93d9c4f, org.springframework.boot.test.context.SpringBootTestAnnotation@74ebcb4a], resourceBasePath = "src/main/webapp", contextLoader = org.springframework.boot.test.context.SpringBootContextLoader, parent = null]
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:180)
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:130)
at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:191)
at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:130)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:260)
at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:163)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179)
at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1625)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at java.base/java.util.stream.StreamSpliterators$WrappingSpliterator.forEachRemaining(StreamSpliterators.java:310)
at java.base/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:735)
at java.base/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:734)
at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:762)
at java.base/java.util.Optional.orElseGet(Optional.java:364)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
Caused by: com.github.tomakehurst.wiremock.common.FatalStartupException: com.github.tomakehurst.wiremock.common.FatalStartupException: java.io.IOException: Failed to bind to /0.0.0.0:8141
at com.github.tomakehurst.wiremock.WireMockServer.start(WireMockServer.java:171)
at org.wiremock.spring.internal.WireMockServerCreator.createWireMockServer(WireMockServerCreator.java:94)
at org.wiremock.spring.internal.WireMockContextCustomizer.resolveOrCreateWireMockServer(WireMockContextCustomizer.java:57)
at org.wiremock.spring.internal.WireMockContextCustomizer.customizeContext(WireMockContextCustomizer.java:47)
at org.springframework.boot.test.context.SpringBootContextLoader$ContextCustomizerAdapter.initialize(SpringBootContextLoader.java:443)
at org.springframework.boot.SpringApplication.applyInitializers(SpringApplication.java:627)
at org.springframework.boot.SpringApplication.prepareContext(SpringApplication.java:401)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:334)
at org.springframework.boot.test.context.SpringBootContextLoader.lambda$loadContext$3(SpringBootContextLoader.java:137)
at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58)
at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46)
at org.springframework.boot.SpringApplication.withHook(SpringApplication.java:1463)
at org.springframework.boot.test.context.SpringBootContextLoader$ContextLoaderHook.run(SpringBootContextLoader.java:553)
at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:137)
at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:108)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:225)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:152)
... 17 more
Caused by: com.github.tomakehurst.wiremock.common.FatalStartupException: java.io.IOException: Failed to bind to /0.0.0.0:8141
at com.github.tomakehurst.wiremock.jetty.JettyHttpServer.start(JettyHttpServer.java:149)
at com.github.tomakehurst.wiremock.WireMockServer.start(WireMockServer.java:169)
... 33 more
Caused by: java.io.IOException: Failed to bind to /0.0.0.0:8141
at org.eclipse.jetty.server.ServerConnector.openAcceptChannel(ServerConnector.java:349)
at org.eclipse.jetty.server.ServerConnector.open(ServerConnector.java:313)
at org.eclipse.jetty.server.Server.lambda$doStart$0(Server.java:569)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179)
at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:992)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596)
at org.eclipse.jetty.server.Server.doStart(Server.java:565)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:93)
at com.github.tomakehurst.wiremock.jetty.JettyHttpServer.start(JettyHttpServer.java:144)
... 34 more
Caused by: java.net.BindException: Address already in use
at java.base/sun.nio.ch.Net.bind0(Native Method)
at java.base/sun.nio.ch.Net.bind(Net.java:555)
at java.base/sun.nio.ch.ServerSocketChannelImpl.netBind(ServerSocketChannelImpl.java:337)
at java.base/sun.nio.ch.ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:294)
Expectation is wiremock server would use normal behavior with stoping once all test methods in a test class are completed so that the next test class can start another wiremock server up on the same port. I can confirm that without @SpyBean being used that this happens as expected.
Reproduction steps
spring boot version 3.3.7
any wiremock-spring-boot version
Run multiple @SpringBootTest integration test classes (not ran in parallel) that mock the same port with a spring-boot-test @SpyBean in at least one of them:
ServiceA
package com.uk;
import org.springframework.stereotype.Component;
@Component
public class ServiceA {
}
Integration Test Class 1 example
package com.uk;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.wiremock.spring.ConfigureWireMock;
import org.wiremock.spring.EnableWireMock;
import com.uk.ServiceA;
@SpringBootTest
@EnableWireMock({@ConfigureWireMock(name = "service", port = 8141)})
class IntegrationTestOne {
@SpyBean private ServiceA serviceA;
@Test
void testWiremock() {
assertThat(1 + 2).isEqualTo(3);
}
}
Integration Test Class 2 example
package com.uk;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.wiremock.spring.ConfigureWireMock;
import org.wiremock.spring.EnableWireMock;
@SpringBootTest
@EnableWireMock({@ConfigureWireMock(name = "service", port = 8141)})
class IntegrationTestTwo {
@Test
void testWiremock() {
assertThat(3 + 1).isEqualTo(4);
}
}
References
No response