🤖

WireMockをTestContainersで立ち上げてJUnitテストを実行する方法

2022/08/07に公開

JUnit5でWireMockを起動する際には、@WireMockTestをクラスにつけて実行することが出来ますが、起動には時間がかかるためテスト起動時にコンテナを利用して1回だけ起動して後はそれを使い回すという方法になります。

今回コンテナの起動には TestContainers を利用します。

build.gradle

今回の記事で利用しているライブラリとバージョンです。
SpringBootは2.7.1を利用しています。

implementation 'org.springframework.boot:spring-boot-starter-webflux'
testImplementation 'com.github.tomakehurst:wiremock-jre8:2.33.2'
testImplementation 'org.testcontainers:testcontainers:1.17.3'

共通クラス

コンテナをシングルトンで起動するため、共通クラスを用意します。

GenericContainerでWireMockのコンテナイメージを起動します。staticで生成することでシングルトンとして扱われ、テストクラス実行の都度コンテナを立ち上げないように設定出来ます。参考: TestContainers -> Singleton containers

また起動したコンテナイメージへの接続portをWireMockインスタンスの生成に利用し、そのインスタンスを WireMock.configureFor に指定することで、テストクラスで stubFor などのスタブ生成で作成されたイメージへその設定が反映されるようになります。

application.ymlなどでAPI通信先の設定がしてある場合には、@DynamicPropertySource を利用して接続先を切り替えておきます。

abstract class AbstractIntegrationTest {
  private static final int WIREMOCK_ORIGINAL_PORT = 8080;

  static final GenericContainer<?> wireMockImage =
      new GenericContainer<>(DockerImageName.parse("wiremock/wiremock:2.33.2"))
          .withExposedPorts(WIREMOCK_ORIGINAL_PORT);
  static WireMock wireMock;

  static {
    wireMockImage.start();
    wireMock = new WireMock(wireMockImage.getMappedPort(WIREMOCK_ORIGINAL_PORT));
    WireMock.configureFor(wireMock);
  }

  // ここでapplication.ymlの設定値を変えてAPIの通信先をWireMockに切り替える
  @DynamicPropertySource
  static void registerProperties(DynamicPropertyRegistry registry) {
    registry.add(
        "api.hello-server.url",
        () ->
            "http://"
                + wireMockImage.getHost()
                + ":"
                + wireMockImage.getMappedPort(WIREMOCK_ORIGINAL_PORT));
  }
}

テストクラス

テストクラスでは、共通クラスを継承してあげればあとはWireMockの通常の使い方(stubForの利用)が可能です。

WireMockのMappingsが残って影響があるようなケースもあるため、BeforeEachで wireMock.removeMappings(); を呼び出してあげれば都度都度Mappingsが削除することが可能です。

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class HelloIntegrationTest extends AbstractIntegrationTest {
  @Autowired private WebTestClient webTestClient;

  @BeforeEach
  void setup() {
    wireMock.removeMappings();
  }

  @Test
  void Taroを返す() {
    stubFor(get("/hello/").willReturn(okJson(" { \"name\": \"taro\", \"age\": 20 }")));

    webTestClient
        .get()
        .uri("/hello")
        .exchange()
        .expectStatus().isOk()
        .expectBody()
        .jsonPath("$.name")
        .isEqualTo("taro");
  }

  @Test
  void Hanakoを返す() {
    stubFor(get("/hello/").willReturn(okJson(" { \"name\": \"hanako\", \"age\": 22 }")));

    webTestClient
        .get()
        .uri("/hello")
        .exchange()
        .expectStatus().isOk()
        .expectBody()
        .jsonPath("$.name")
        .isEqualTo("hanako");
  }
}

参考情報

Discussion