これは、なにをしたくて書いたもの?
1度、WireMockを試しておきたいなということで。
WireMock - flexible, open source API mocking | WireMock
WireMock
WireMockは、APIをモックすることができるライブラリーです。スタンドアロンなサーバーとしても使えます。
WireMock - flexible, open source API mocking | WireMock
WireMockの概要はこちら。
コード、REST API、JSONファイル、別サーバーに送信されたHTTPトラフィックを記録するプロキシとしてなど、複数の方法で
モックAPIを作成できます。Handlerbarsベースのテンプレートを導入していて、動的なレスポンスも生成できるようです。
以下が主要な機能です。
- URLやヘッダー、HTTPボディのパターンにマッチしたHTTPレスポンスのスタブ
- リクエストの検証
- ユニットテスト、スタンドアロンプロセス、WARとして実行可能
- スタブの記録および再生
- レスポンスの遅延設定やフォールトインジェクション
- リクエストごとの条件付きプロキシ
- リクエストのインスペクションと置換のためのブラウザプロキシ
- ステートフルな振る舞いのシミュレーション
JUnit 4を使ったQuick Startはこちら。
API Mocking QuickStart with Java and JUnit 4 | WireMock
各実行形態についてまとめたコンテンツはこちら。
- WireMock as a Standalone Service
- Java and Java Virtual Machine Solutions | WireMock
- Deploying into a servlet container | WireMock
スタブの作成や検証。
Returning stubbed HTTP responses to specific requests | WireMock
Spring Bootとのインテグレーションもあるようです。
Using WireMock's Spring Boot + JUnit 5 integration | WireMock
ひとまず、ドキュメントとしてはこのあたりに目を通しておけばよいかなと思います。
JUnit 5にも対応しているようなので、今回はこちらを使ってJavaコードでAPIのモックを書いてみたいと思います。
Using WireMock's JUnit Jupiter extension | WireMock
環境
今回の環境はこちら。
$ java --version openjdk 21.0.7 2025-04-15 OpenJDK Runtime Environment (build 21.0.7+6-Ubuntu-0ubuntu124.04) OpenJDK 64-Bit Server VM (build 21.0.7+6-Ubuntu-0ubuntu124.04, mixed mode, sharing) $ mvn --version Apache Maven 3.9.9 (8e8579a9e76f7d015ee5ec7bfcdc97d260186937) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 21.0.7, vendor: Ubuntu, runtime: /usr/lib/jvm/java-21-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "6.8.0-60-generic", arch: "amd64", family: "unix"
準備
Maven依存関係などはこちら。
<properties> <maven.compiler.release>21</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties> <dependencies> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-client</artifactId> <version>6.2.12.Final</version> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jackson2-provider</artifactId> <version>6.2.12.Final</version> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.12.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.27.3</version> <scope>test</scope> </dependency> <dependency> <groupId>org.wiremock</groupId> <artifactId>wiremock</artifactId> <version>3.13.0</version> <scope>test</scope> </dependency> </dependencies>
WireMockの依存関係はこちらですね。
<dependency> <groupId>org.wiremock</groupId> <artifactId>wiremock</artifactId> <version>3.13.0</version> <scope>test</scope> </dependency>
HTTPクライアントとしては、Jakarta RESTful Web Services(以降JAX-RS)のクライアントを使用します。実装はRESTEasyです。
WireMockを使ってみる
それでは、WireMockを使ってみましょう。
簡単に使ってみるパターンと、テンプレートを使ってみるパターンをやってみます。
テストコードの雛形はこちら。
src/test/java/WireMockGettingStartedTest.java
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; import com.github.tomakehurst.wiremock.junit5.WireMockTest; import jakarta.ws.rs.client.Client; import jakarta.ws.rs.client.ClientBuilder; import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import java.util.Map; import org.junit.jupiter.api.Test; import static com.github.tomakehurst.wiremock.client.WireMock.*; import static org.assertj.core.api.Assertions.assertThat; // @WireMockTest(httpPort = 8080) // WireMockがリッスンするポートを固定する場合 @WireMockTest class WireMockGettingStartedTest { // ここに、テストを書く!! }
JUnit 5向けのExtensionを使っています。
Using WireMock's JUnit Jupiter extension | WireMock
こちらですね。
// @WireMockTest(httpPort = 8080) // WireMockがリッスンするポートを固定する場合 @WireMockTest class WireMockGettingStartedTest {
デフォルトでは、WireMockは空いているランダムなポートで起動します。コメントアウトしていますが、ポートを固定する場合は
httpPortを指定します。
WireMockが起動するとHTTPサーバーが起動するわけですが、アクセスする際に必要な情報はテストメソッドの引数として
受け取れます。
@Test void simple(WireMockRuntimeInfo wmRuntimeInfo) {
簡単な使い方。
@Test void simple(WireMockRuntimeInfo wmRuntimeInfo) { assertThat(wmRuntimeInfo.getHttpBaseUrl()).contains("http://localhost:"); stubFor( get("/message") .willReturn(ok() .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) .withBody(""" {"message": "Hello World / GET"} """ ) ) ); stubFor( post("/message") .withHeader(HttpHeaders.CONTENT_TYPE, containing("json")) .willReturn(ok() .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) .withBody(""" {"message": "Hello World / POST"} """ ) ) ); try (Client client = ClientBuilder.newBuilder().build()) { try (Response getResponse = client.target(wmRuntimeInfo.getHttpBaseUrl() + "/message").request().get()) { assertThat(getResponse.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); assertThat(getResponse.readEntity(Map.class)) .isEqualTo(Map.of("message", "Hello World / GET")); } try (Response postReponse = client.target(wmRuntimeInfo.getHttpBaseUrl() + "/message") .request() .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) .post(Entity.entity(Map.of("parameter", "value"), MediaType.APPLICATION_JSON))) { assertThat(postReponse.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); assertThat(postReponse.readEntity(Map.class)) .isEqualTo(Map.of("message", "Hello World / POST")); } } verify(1, getRequestedFor(urlEqualTo("/message"))); verify(1, postRequestedFor(urlEqualTo("/message")) .withHeader(HttpHeaders.CONTENT_TYPE, equalTo(MediaType.APPLICATION_JSON)) .withRequestBody(equalTo(""" {"parameter":"value"}\ """) )); }
WireMockにアクセスするためのベースURLは、WireMockRuntimeInfo#getHttpBaseUrlで取得します。http://localhost:[ポート]の
イメージですね。
assertThat(wmRuntimeInfo.getHttpBaseUrl()).contains("http://localhost:");
リクエストを受け付けるエンドポイントおよびレスポンスの定義。要するにAPIのモックです。
stubFor(
get("/message")
.willReturn(ok()
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
.withBody("""
{"message": "Hello World / GET"}
"""
)
)
);
stubFor(
post("/message")
.withHeader(HttpHeaders.CONTENT_TYPE, containing("json"))
.willReturn(ok()
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
.withBody("""
{"message": "Hello World / POST"}
"""
)
)
);
このあたりを参考にして作成します。
Returning stubbed HTTP responses to specific requests | WireMock
Matching and filtering HTTP requests in WireMock | WireMock
モックで設定したとおりのレスポンスが得られることを確認。
try (Client client = ClientBuilder.newBuilder().build()) { try (Response getResponse = client.target(wmRuntimeInfo.getHttpBaseUrl() + "/message").request().get()) { assertThat(getResponse.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); assertThat(getResponse.readEntity(Map.class)) .isEqualTo(Map.of("message", "Hello World / GET")); } try (Response postReponse = client.target(wmRuntimeInfo.getHttpBaseUrl() + "/message") .request() .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) .post(Entity.entity(Map.of("parameter", "value"), MediaType.APPLICATION_JSON))) { assertThat(postReponse.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); assertThat(postReponse.readEntity(Map.class)) .isEqualTo(Map.of("message", "Hello World / POST")); } }
WireMockが受け付けたリクエストに対するアサーションもできます。
verify(1, getRequestedFor(urlEqualTo("/message"))); verify(1, postRequestedFor(urlEqualTo("/message")) .withHeader(HttpHeaders.CONTENT_TYPE, equalTo(MediaType.APPLICATION_JSON)) .withRequestBody(equalTo(""" {"parameter":"value"}\ """) ));
Verifying whether specific HTTP requests were made | WireMock
ちなみに定義したAPIモックの設定は、デフォルトでテストごとにリセットされるようです。
このあたりですね。
続いて、テンプレートを使ってみましょう。テンプレートを使うことで、動的なレスポンスを作成することができます。
Mock API Response Templating | WireMock
こんな感じで使ってみました。
@Test void template(WireMockRuntimeInfo wmRuntimeInfo) { stubFor( get(urlMatching("/message\\?word=(.+)")) .willReturn(aResponse() .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) .withBody(""" {"message": "Hello World", "method": "GET", "from": "{{request.query.word}}"} """ ) .withTransformers("response-template") ) ); stubFor( post("/message") .withHeader(HttpHeaders.CONTENT_TYPE, containing("json")) .willReturn(aResponse() .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) .withBody(""" {"message": "Hello World", "method": "POST", "from": "{{jsonPath request.body '$.word'}}"} """ ) .withTransformers("response-template") ) ); try (Client client = ClientBuilder.newBuilder().build()) { try (Response getResponse = client.target(wmRuntimeInfo.getHttpBaseUrl() + "/message?word=WireMock").request().get()) { assertThat(getResponse.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); assertThat(getResponse.readEntity(Map.class)) .isEqualTo(Map.of("message", "Hello World", "method", "GET", "from", "WireMock")); } try (Response postReponse = client.target(wmRuntimeInfo.getHttpBaseUrl() + "/message") .request() .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) .post(Entity.entity(Map.of("word", "WireMock"), MediaType.APPLICATION_JSON))) { assertThat(postReponse.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); assertThat(postReponse.readEntity(Map.class)) .isEqualTo(Map.of("message", "Hello World", "method", "POST", "from", "WireMock")); } } verify(1, getRequestedFor(urlMatching("/message\\?word=.+")) .withQueryParam("word", equalTo("WireMock"))); verify(1, postRequestedFor(urlEqualTo("/message")) .withHeader(HttpHeaders.CONTENT_TYPE, equalTo(MediaType.APPLICATION_JSON)) .withRequestBody(equalTo(""" {"word":"WireMock"}\ """) )); }
モックを定義しているのはこちら。
stubFor(
get(urlMatching("/message\\?word=(.+)"))
.willReturn(aResponse()
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
.withBody("""
{"message": "Hello World", "method": "GET", "from": "{{request.query.word}}"}
"""
)
.withTransformers("response-template")
)
);
stubFor(
post("/message")
.withHeader(HttpHeaders.CONTENT_TYPE, containing("json"))
.willReturn(aResponse()
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
.withBody("""
{"message": "Hello World", "method": "POST", "from": "{{jsonPath request.body '$.word'}}"}
"""
)
.withTransformers("response-template")
)
);
ポイントはレスポンスの定義をaResponseで開始して、withTransformersでresponse-templateを設定していることです。
これで、こんな感じでHandlerbarsのテンプレートを使えます。
.withBody(""" {"message": "Hello World", "method": "GET", "from": "{{request.query.word}}"} """ ) .withBody(""" {"message": "Hello World", "method": "POST", "from": "{{jsonPath request.body '$.word'}}"} """ )
今回はGETの時はQueryStringを、POSTの時はボディの中のJSONの一部を使っています。
どのようなパラメーターが使えるかは、このあたりを見るとよいでしょう。
Response Templating / The request model
またヘルパーという関数を使うこともできます。
Response Templating / Handlebars helpers
たとえば、こちらのjsonPathというのはJSONPathヘルパーです。
.withBody(""" {"message": "Hello World", "method": "POST", "from": "{{jsonPath request.body '$.word'}}"} """ )
Response Templating / JSONPath helper
あとは、リクエストの内容がレスポンスに反映されることを確認。
try (Client client = ClientBuilder.newBuilder().build()) { try (Response getResponse = client.target(wmRuntimeInfo.getHttpBaseUrl() + "/message?word=WireMock").request().get()) { assertThat(getResponse.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); assertThat(getResponse.readEntity(Map.class)) .isEqualTo(Map.of("message", "Hello World", "method", "GET", "from", "WireMock")); } try (Response postReponse = client.target(wmRuntimeInfo.getHttpBaseUrl() + "/message") .request() .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) .post(Entity.entity(Map.of("word", "WireMock"), MediaType.APPLICATION_JSON))) { assertThat(postReponse.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); assertThat(postReponse.readEntity(Map.class)) .isEqualTo(Map.of("message", "Hello World", "method", "POST", "from", "WireMock")); } }
こんなところでしょうか。
おわりに
APIのモックサーバーを定義してテストに使える、WireMockを試してみました。
どういうものだろうと思っていたのですが、けっこう簡単に使えて便利でした。
今回はJavaのテストコード内で扱ってみましたが、JSONで設定したり、スタンドアロンなサーバーとして使う場合でも
使い方にはそれほど困らない気がします。