使用 Spring Boot JUnit 5 测试的 Wiremock:测试后使用的地址 运行
Wiremock with Spring Boot JUnit 5 test: Address in Use after test run
我们有一个 Spring Boot 2.2.0.RELEASE 应用程序,我们正在使用 WireMock 通过 JUnit 5 测试 class 对其进行测试。测试 运行 在本地很好,但在我们的 Jenkins 上,测试失败 "Address already in use" 消息 在测试 运行 成功 .
这是来自 pom.xml
的 spring 依赖项:
<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>
所以我们的测试非常简单,看起来像这样:
@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(" ", "")));
}
}
同样,这 运行 在本地没问题,在 Jenkins 上,实际测试用例通过了,但随后我们得到错误:
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(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(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
我还为 Spring WireMockConfiguration
启用了 DEBUG 日志记录,有趣的是,然后构建成功。测试后确实提到"Resetting mappings for the next test to restart them. That's necessary when reusing the same context with new servers running on random ports"。
这让我觉得这可能是某种竞争条件,但我不能说我完全掌握了整体设置。
任何指点都会有所帮助。
这是一个已知问题:https://github.com/spring-cloud/spring-cloud-contract/issues/665
您必须在所有使用 WireMock 的测试中使用 @DirtiesContext
或在 src/test/resources/spring.properties 中设置 spring.test.context.cache.maxSize=1
(https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#testcontext-ctx-management-caching)
Spring-启动:2.5.4
使用 @DirtiesContext
是我们最后的资源。
Note: Use a random port
或者:
- 作为注释传递 属性:
@AutoConfigureWireMock(port = 0)
。
Note: Keep in mind that default port is 8080
, so could be a port conflict with local service or others running locally (if server.port
has a default value / not specified
).
- 或在
application-test.properties/.yml
中:wiremock.server.port=0
.
我建议最后一个,因为它更易于维护,将配置保存在一个地方以供查询。
Can reuse this on multiple endpoints (third party or local services):
app:
endpoints:
local-service: http://localhost:${wiremock.server.port}/some/path
external-service: http://localhost:${wiremock.server.port}/another/path
我们有一个 Spring Boot 2.2.0.RELEASE 应用程序,我们正在使用 WireMock 通过 JUnit 5 测试 class 对其进行测试。测试 运行 在本地很好,但在我们的 Jenkins 上,测试失败 "Address already in use" 消息 在测试 运行 成功 .
这是来自 pom.xml
的 spring 依赖项:
<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>
所以我们的测试非常简单,看起来像这样:
@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(" ", "")));
}
}
同样,这 运行 在本地没问题,在 Jenkins 上,实际测试用例通过了,但随后我们得到错误:
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(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(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
我还为 Spring WireMockConfiguration
启用了 DEBUG 日志记录,有趣的是,然后构建成功。测试后确实提到"Resetting mappings for the next test to restart them. That's necessary when reusing the same context with new servers running on random ports"。
这让我觉得这可能是某种竞争条件,但我不能说我完全掌握了整体设置。
任何指点都会有所帮助。
这是一个已知问题:https://github.com/spring-cloud/spring-cloud-contract/issues/665
您必须在所有使用 WireMock 的测试中使用 @DirtiesContext
或在 src/test/resources/spring.properties 中设置 spring.test.context.cache.maxSize=1
(https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#testcontext-ctx-management-caching)
Spring-启动:2.5.4
使用 @DirtiesContext
是我们最后的资源。
Note: Use a random port
或者:
- 作为注释传递 属性:
@AutoConfigureWireMock(port = 0)
。
Note: Keep in mind that default port is
8080
, so could be a port conflict with local service or others running locally (ifserver.port
has adefault value / not specified
).
- 或在
application-test.properties/.yml
中:wiremock.server.port=0
.
我建议最后一个,因为它更易于维护,将配置保存在一个地方以供查询。
Can reuse this on multiple endpoints (third party or local services):
app:
endpoints:
local-service: http://localhost:${wiremock.server.port}/some/path
external-service: http://localhost:${wiremock.server.port}/another/path