Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wiremock with Spring Boot JUnit 5 test: Address in Use after test run

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.

like image 645
daniu Avatar asked Nov 25 '19 10:11

daniu


People also ask

What is the use of WireMock in spring boot?

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.

What is spring cloud contract WireMock?

Spring Cloud Contract provides a convenience class that can load JSON WireMock stubs into a Spring MockRestServiceServer .

How do you use kotlin WireMock?

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.


1 Answers

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)

like image 91
Mateusz Rasiński Avatar answered Oct 01 '22 08:10

Mateusz Rasiński