💯
RestClient + WireMockで一部のテストだけ失敗する現象+対策
真の原因は分かっていなくて、とりあえずこうしたら動いたというレベルの記事です。
環境
- JDK 25
- Spring Boot 4.0.1
- WireMock 3.13.2
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>rest-client-sample</artifactId>
<version>1.0.0</version>
<properties>
<java.version>25</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-restclient</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-restclient-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock-standalone</artifactId>
<version>3.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
テスト対象
@Component
public class TodoClient {
private final RestClient restClient;
public TodoClient(
RestClient.Builder restClientBuilder,
@Value("${todo-service.base-url}") String baseUrl
) {
this.restClient = restClientBuilder
.baseUrl(baseUrl)
.build();
}
// GET・POST・PUT・DELETE・PATCHなどのメソッドが色々ある
}
application.properties
spring.application.name=rest-client-sample
# 接続タイムアウト
spring.http.clients.connect-timeout=1s
# 読み取りタイムアウト
spring.http.clients.read-timeout=1s
# JdkClientHttpRequestFactoryを使う
spring.http.clients.imperative.factory=jdk
todo-service.base-url=http://localhost:9999
テストコード
@WireMockTest(httpPort = 9999)
@SpringBootTest
public class TodoClientTest {
@Autowired
TodoClient todoClient;
@Nested
@DisplayName("getById()")
class GetByIdTest {
@Test
@DisplayName("IDを指定すると、該当するTODOのOptionalを取得できる")
void success() {
// WireMockの設定
stubFor(get("/api/todos/1")
.willReturn(okJson("""
{
"id": 1,
"description": "Example 1",
"completed": true,
"deadline": "2025-10-01T12:00:00",
"createdAt": "2025-09-01T12:00:00"
}
""")));
// テストの実行
Optional<TodoResponse> actual = todoClient.getById(1);
// 結果の検証
assertEquals(
new TodoResponse(
1,
"Example 1",
true,
LocalDateTime.parse("2025-10-01T12:00:00"),
LocalDateTime.parse("2025-09-01T12:00:00")
), actual.get()
);
}
// 他にもテストがたくさん
テストを実行すると
なぜか一部のテストだけ成功して、その他のほとんどがNGになる。

エラーメッセージはこんな感じ。
org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://localhost:9999/api/todos": null
at org.springframework.web.client.DefaultRestClient$DefaultRequestBodyUriSpec.createResourceAccessException(DefaultRestClient.java:763)
at org.springframework.web.client.DefaultRestClient$DefaultRequestBodyUriSpec.exchangeInternal(DefaultRestClient.java:615)
at org.springframework.web.client.DefaultRestClient$DefaultRequestBodyUriSpec.exchange(DefaultRestClient.java:567)
at org.springframework.web.client.RestClient$RequestHeadersSpec.exchange(RestClient.java:750)
at org.springframework.web.client.DefaultRestClient$DefaultResponseSpec.executeAndExtract(DefaultRestClient.java:896)
at org.springframework.web.client.DefaultRestClient$DefaultResponseSpec.toBodilessEntity(DefaultRestClient.java:860)
at com.example.restclientsample.TodoClient.register(TodoClient.java:55)
at com.example.restclientsample.TodoClientTest$RegisterTest.success(TodoClientTest.java:165)
Caused by: java.net.ConnectException
at java.net.http/jdk.internal.net.http.common.Utils.toConnectException(Utils.java:1070)
at java.net.http/jdk.internal.net.http.PlainHttpConnection.connectAsync(PlainHttpConnection.java:223)
at ...
Caused by: java.nio.channels.ClosedChannelException
at java.base/sun.nio.ch.SocketChannelImpl.ensureOpen(SocketChannelImpl.java:204)
at java.base/sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:942)
at java.net.http/jdk.internal.net.http.PlainHttpConnection.connectAsync(PlainHttpConnection.java:211)
... 139 more
対策1: @RegisterExtensionを使う
@WireMockTestではなく、@RegisterExtensionを使ってみた。
@SpringBootTest
public class TodoClientTest {
@Autowired
TodoClient todoClient;
// コレ!!!
@RegisterExtension
static WireMockExtension wireMock = WireMockExtension.newInstance()
.options(wireMockConfig()
.port(9999))
.build();
@Nested
@DisplayName("getById()")
class GetByIdTest {
@Test
@DisplayName("IDを指定すると、該当するTODOのOptionalを取得できる")
void success() {
// ここも少し変更!!!
wireMock.stubFor(get("/api/todos/1")
...
}
// 他にもテストがたくさん
これでテストを実行すると、OKになるテストは増えたけど、まだNGなテストが残っている。

エラーメッセージはこんな感じ。
org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://localhost:9999/api/todos": Received RST_STREAM: Stream cancelled
at org.springframework.web.client.DefaultRestClient$DefaultRequestBodyUriSpec.createResourceAccessException(DefaultRestClient.java:763)
at org.springframework.web.client.DefaultRestClient$DefaultRequestBodyUriSpec.exchangeInternal(DefaultRestClient.java:615)
at org.springframework.web.client.DefaultRestClient$DefaultRequestBodyUriSpec.exchange(DefaultRestClient.java:567)
at org.springframework.web.client.RestClient$RequestHeadersSpec.exchange(RestClient.java:750)
at org.springframework.web.client.DefaultRestClient$DefaultResponseSpec.executeAndExtract(DefaultRestClient.java:896)
at org.springframework.web.client.DefaultRestClient$DefaultResponseSpec.toBodilessEntity(DefaultRestClient.java:860)
at com.example.restclientsample.TodoClient.register(TodoClient.java:55)
at com.example.restclientsample.TodoClientTest$RegisterTest.success(TodoClientTest.java:163)
Caused by: java.io.IOException: Received RST_STREAM: Stream cancelled
at java.net.http/jdk.internal.net.http.Stream.handleReset(Stream.java:778)
at java.net.http/jdk.internal.net.http.Stream.incoming_reset(Stream.java:709)
at java.net.http/jdk.internal.net.http.Stream.otherFrame(Stream.java:588)
at java.net.http/jdk.internal.net.http.Stream.incoming(Stream.java:581)
at java.net.http/jdk.internal.net.http.Http2Connection.processFrame(Http2Connection.java:1072)
at java.net.http/jdk.internal.net.http.frame.FramesDecoder.decode(FramesDecoder.java:155)
at java.net.http/jdk.internal.net.http.Http2Connection$FramesController.processReceivedData(Http2Connection.java:318)
at java.net.http/jdk.internal.net.http.Http2Connection.asyncReceive(Http2Connection.java:871)
at java.net.http/jdk.internal.net.http.Http2Connection$Http2TubeSubscriber.processQueue(Http2Connection.java:1840)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$LockingRestartableTask.run(SequentialScheduler.java:182)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$CompleteRestartableTask.run(SequentialScheduler.java:149)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SchedulableTask.run(SequentialScheduler.java:207)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1090)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:614)
at java.base/java.lang.Thread.run(Thread.java:1474)
対策2: HTTP2を無効化
@SpringBootTest
public class TodoClientTest {
@Autowired
TodoClient todoClient;
@RegisterExtension
static WireMockExtension wireMock = WireMockExtension.newInstance()
.options(wireMockConfig()
// コレ!!!
.http2PlainDisabled(true)
.port(9999))
.build();
これでテストがすべてOKになりました!

Discussion