Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wiremock server not stopping between test classes when @SpyBean used #82

Open
iranicus opened this issue Jan 23, 2025 · 2 comments
Open
Labels
bug Something isn't working

Comments

@iranicus
Copy link

iranicus commented Jan 23, 2025

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

@iranicus iranicus added the bug Something isn't working label Jan 23, 2025
@tomasbjerre
Copy link
Collaborator

I think it actually is stopping, but not fast enough:

And the annotation triggers a new ApplicationContext. Without it, the same ApplicationContext is re-used. There are 2 problems:

Why do you need to use a static port?

tomasbjerre added a commit that referenced this issue Jan 23, 2025
@iranicus
Copy link
Author

Just because its the port we would normally use for the service which makes the integration test more realistic instead of switching port. The static port is currently set in our test application.yml and loaded into the service via dependency injection. Previously we were using the stub contract runner with @AutoConfigureWiremock(8141) which worked fine before switching over to wiremock-spring-boot.

I think it actually is stopping, but not fast enough:

wiremock-spring-boot/src/main/java/org/wiremock/spring/internal/WireMockServerCreator.java

Line 107 in 1b42d76

newServer.stop();
And the annotation triggers a new ApplicationContext. Without it, the same ApplicationContext is re-used. There are 2 problems:

  • The server is not found in the Store, because it is stored with another ApplicationContext key

      [wiremock-spring-boot/src/main/java/org/wiremock/spring/internal/Store.java](https://github.com/wiremock/wiremock-spring-boot/blob/1b42d760c0496da2c9353ae70c0947e17ca6169c/src/main/java/org/wiremock/spring/internal/Store.java#L24)
    
    
         Line 24
      in
      [1b42d76](/wiremock/wiremock-spring-boot/commit/1b42d760c0496da2c9353ae70c0947e17ca6169c)
    
    
    
    
    
        
          
           private final Map<ApplicationContext, Map<String, WireMockServer>> store =
    
  • The server that is running, is shutting down and cannot be re-used safely.

Why do you need to use a static port?

Just because its the port we would normally use for the service which makes the integration test more realistic instead of switching port. The static port is currently set in our test application.yml and loaded into the service via dependency injection. Previously we were using the stub contract runner with @AutoConfigureWiremock(8141) which worked fine before switching over to wiremock-spring-boot.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants