以下の内容はhttps://kazuhira-r.hatenablog.com/entry/2025/05/21/002206より取得しました。


APIのモックサーバーを定義してテストに使える、WireMockを試す

これは、なにをしたくて書いたもの?

1度、WireMockを試しておきたいなということで。

WireMock - flexible, open source API mocking | WireMock

WireMock

WireMockは、APIをモックすることができるライブラリーです。スタンドアロンなサーバーとしても使えます。

WireMock - flexible, open source API mocking | WireMock

WireMockの概要はこちら。

WireMock Overview and Basics

コード、REST APIJSONファイル、別サーバーに送信されたHTTPトラフィックを記録するプロキシとしてなど、複数の方法で
モックAPIを作成できます。Handlerbarsベースのテンプレートを導入していて、動的なレスポンスも生成できるようです。

以下が主要な機能です。

  • URLやヘッダー、HTTPボディのパターンにマッチしたHTTPレスポンスのスタブ
  • リクエストの検証
  • ユニットテストスタンドアロンプロセス、WARとして実行可能
  • スタブの記録および再生
  • レスポンスの遅延設定やフォールトインジェクション
  • リクエストごとの条件付きプロキシ
  • リクエストのインスペクションと置換のためのブラウザプロキシ
  • ステートフルな振る舞いのシミュレーション

JUnit 4を使ったQuick Startはこちら。

API Mocking QuickStart with Java and JUnit 4 | 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モックの設定は、デフォルトでテストごとにリセットされるようです。

Stubbing / Reset

このあたりですね。

wiremock/src/main/java/com/github/tomakehurst/wiremock/junit5/WireMockExtension.java at 3.13.0 · wiremock/wiremock · GitHub

続いて、テンプレートを使ってみましょう。テンプレートを使うことで、動的なレスポンスを作成することができます。

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で開始して、withTransformersresponse-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で設定したり、スタンドアロンなサーバーとして使う場合でも
使い方にはそれほど困らない気がします。




以上の内容はhttps://kazuhira-r.hatenablog.com/entry/2025/05/21/002206より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14