We have a Spring Boot 2.2.0.RELEASE application that we're testing with a JUnit 5 test class using WireMock. The test runs fine locally, but on our Jenkins, it fails with an "Address already in use" message after the test has run successfully.
Here's our spring dependencies from pom.xml
:
<properties>
<java.version>11</java.version>
<spring-cloud.version>Hoxton.RC2</spring-cloud.version>
<spring-cloud-stream.version>3.0.0.RC2</spring-cloud-stream.version>
<openapi.codegen.maven.plugin.version>4.1.2</openapi.codegen.maven.plugin.version>
<jacoco-maven-plugin.version>0.8.4</jacoco-maven-plugin.version>
</properties>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<!-- Utils -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator</artifactId>
<version>${openapi.codegen.maven.plugin.version}</version>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-wiremock</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
<version>${spring-cloud-stream.version}</version>
<type>test-jar</type>
<scope>test</scope>
<classifier>test-binder</classifier>
</dependency>
</dependencies>
So our test is pretty simple and looks like this:
@ExtendWith(SpringExtension.class)
@SpringBootTest
@ActiveProfiles("test")
@AutoConfigureWireMock(port = 0)
@Import(TestChannelBinderConfiguration.class)
class OurTestClass {
@Autowired
private OurDataCache cache;
@Autowired
private InputDestination source;
@Autowired
private OutputDestination target;
@BeforeEach
void setupApi() throws IOException, URISyntaxException {
stubFor(get("/endpoint")
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE)
.withBody(data())
)
);
}
@Test
void sampleTest() {
String messageContent = loadResourceFileAsMessage("messageIn.json");
String expectedOutMessage = loadResourceFileAsMessage("messageOut.json");
Message<byte[]> message = new GenericMessage<>(messageContent.getBytes());
source.send(message);
Message<byte[]> received = target.receive();
assertThat(received, notNullValue());
assertThat(new String(received.getPayload()), equalTo(expectedOutMessage.replace(" ", "")));
}
}
Again, this runs fine locally, and on Jenkins, the actual test case passes, but then we get the error:
10:29:40 2019-11-25 09:29:40.683 WARN 414 --- [ main] o.s.test.context.TestContextManager : Caught exception while invoking 'afterTestClass' callback on TestExecutionListener [org.springframework.cloud.contract.wiremock.WireMockTestExecutionListener@2b68c59b] for test class [class our.test.Class]
10:29:40
10:29:40 com.github.tomakehurst.wiremock.common.FatalStartupException: java.lang.RuntimeException: java.io.IOException: Failed to bind to /0.0.0.0:12193
10:29:40 at com.github.tomakehurst.wiremock.WireMockServer.start(WireMockServer.java:148) ~[wiremock-jre8-standalone-2.25.1.jar:na]
10:29:40 at org.springframework.cloud.contract.wiremock.WireMockConfiguration.reRegisterServer(WireMockConfiguration.java:137) ~[spring-cloud-contract-wiremock-2.2.0.RC2.jar:2.2.0.RC2]
10:29:40 at org.springframework.cloud.contract.wiremock.WireMockConfiguration.resetMappings(WireMockConfiguration.java:150) ~[spring-cloud-contract-wiremock-2.2.0.RC2.jar:2.2.0.RC2]
10:29:40 at org.springframework.cloud.contract.wiremock.WireMockTestExecutionListener.afterTestClass(WireMockTestExecutionListener.java:76) ~[spring-cloud-contract-wiremock-2.2.0.RC2.jar:2.2.0.RC2]
10:29:40 at org.springframework.test.context.TestContextManager.afterTestClass(TestContextManager.java:488) ~[spring-test-5.2.0.RELEASE.jar:5.2.0.RELEASE]
10:29:40 at org.springframework.test.context.junit.jupiter.SpringExtension.afterAll(SpringExtension.java:86) ~[spring-test-5.2.0.RELEASE.jar:5.2.0.RELEASE]
10:29:40 at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeAfterAllCallbacks$13(ClassBasedTestDescriptor.java:421) ~[junit-jupiter-engine-5.5.2.jar:5.5.2]
10:29:40 at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.5.2.jar:1.5.2]
10:29:40 at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeAfterAllCallbacks$14(ClassBasedTestDescriptor.java:421) ~[junit-jupiter-engine-5.5.2.jar:5.5.2]
10:29:40 at java.base/java.util.ArrayList.forEach(ArrayList.java:1540) ~[na:na]
10:29:40 at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeAfterAllCallbacks(ClassBasedTestDescriptor.java:421) ~[junit-jupiter-engine-5.5.2.jar:5.5.2]
[...]
10:29:40 at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:126) ~[surefire-booter-2.22.2.jar:2.22.2]
10:29:40 at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:418) ~[surefire-booter-2.22.2.jar:2.22.2]
10:29:40 Caused by: java.lang.RuntimeException: java.io.IOException: Failed to bind to /0.0.0.0:12193
10:29:40 at com.github.tomakehurst.wiremock.jetty9.JettyHttpServer.start(JettyHttpServer.java:184) ~[wiremock-jre8-standalone-2.25.1.jar:na]
10:29:40 at com.github.tomakehurst.wiremock.WireMockServer.start(WireMockServer.java:146) ~[wiremock-jre8-standalone-2.25.1.jar:na]
10:29:40 ... 44 common frames omitted
10:29:40 Caused by: java.io.IOException: Failed to bind to /0.0.0.0:12193
10:29:40 at wiremock.org.eclipse.jetty.server.ServerConnector.openAcceptChannel(ServerConnector.java:346) ~[wiremock-jre8-standalone-2.25.1.jar:na]
[...]
10:29:40 at com.github.tomakehurst.wiremock.jetty9.JettyHttpServer.start(JettyHttpServer.java:182) ~[wiremock-jre8-standalone-2.25.1.jar:na]
10:29:40 ... 45 common frames omitted
10:29:40 Caused by: java.net.BindException: Address already in use
10:29:40 at java.base/sun.nio.ch.Net.bind0(Native Method) ~[na:na]
10:29:40 at java.base/sun.nio.ch.Net.bind(Net.java:461) ~[na:na]
10:29:40 at java.base/sun.nio.ch.Net.bind(Net.java:453) ~[na:na]
10:29:40 at java.base/sun.nio.ch.ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:227) ~[na:na]
10:29:40 at java.base/sun.nio.ch.ServerSocketAdaptor.bind(ServerSocketAdaptor.java:80) ~[na:na]
10:29:40 at wiremock.org.eclipse.jetty.server.ServerConnector.openAcceptChannel(ServerConnector.java:342) ~[wiremock-jre8-standalone-2.25.1.jar:na]
10:29:40 ... 52 common frames omitted
10:29:40
10:29:40 [ERROR] [1;31mTests [0;1mrun: [0;1m2[m, Failures: 0, [1;31mErrors: [0;1;31m1[m, Skipped: 0, Time elapsed: 15.925 s[1;31m <<< FAILURE!
10:29:40 [ERROR] our.test.Class Time elapsed: 1.843 s <<< ERROR!
10:29:40 com.github.tomakehurst.wiremock.common.FatalStartupException: java.lang.RuntimeException: java.io.IOException: Failed to bind to /0.0.0.0:12193
10:29:40 Caused by: java.lang.RuntimeException: java.io.IOException: Failed to bind to /0.0.0.0:12193
10:29:40 Caused by: java.io.IOException: Failed to bind to /0.0.0.0:12193
10:29:40 Caused by: java.net.BindException: Address already in use
I also enabled DEBUG logging for the Spring WireMockConfiguration
, and funnily enough, then the build succeeds. It does mention "Resetting mappings for the next test to restart them. That's necessary when reusing the same context with new servers running on random ports" after the test.
This makes me think this might be some sort of race condition, but I can't say I fully grasp the overall setup.
Any pointers would be helpful.
WireMock is a library for stubbing and mocking web services. It constructs an HTTP server that acts as an actual web service. In simple terms, it is a mock server that is highly configurable and has the ability to return recorded HTTP responses for requests matching criteria.
Spring Cloud Contract provides a convenience class that can load JSON WireMock stubs into a Spring MockRestServiceServer .
First of all, we need to create a new test class with the TestInstance annotation on it. Create the WireMock server with a dynamic port. Start the server and configure it for localhost and the port inside of the BeforeAll annotation. Create a stubFor the url you want to test the get request to.
It's a known issue: https://github.com/spring-cloud/spring-cloud-contract/issues/665
You have to use @DirtiesContext
in all tests that use WireMock or set spring.test.context.cache.maxSize=1
in src/test/resources/spring.properties (https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#testcontext-ctx-management-caching)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With