kagamihogeの日記

kagamihogeの日記です。

spring-cloud-starter-contract-stub-runner(WireMock)によるintegration test

spring-bootで外部アクセスを伴うweb-apiのintegration testを記述する際は何らかのmock serverを必要とする。ここではmock serverにWireMockを使用し、また、spring-bootとWireMockの連携にSpring Cloud Contractを使用する。WireMock関連の記述は6. Spring Cloud Contract WireMockのあたりにある。

ソースコード

build.gradle

Spring Initializr のadd dependencies...で「wiremock」と入力すると候補に出てくるContract Stub Runnerを追加して出来たものを使用する。

plugins {
    id 'org.springframework.boot' version '2.6.6'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

ext {
    set('springCloudVersion', "2021.0.1")
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.cloud:spring-cloud-starter-contract-stub-runner'
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
    }
}

tasks.named('test') {
    useJUnitPlatform()
}

適当な外部アクセスするrest apiを作成する。

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@SpringBootApplication()
public class SampleWireMockApplication {
  @Value("${sample.remote.path}")
  String path;

  @RequestMapping("/sample/{key}")
  public String sample(@PathVariable String key) {
    RestTemplate template = new RestTemplate();

    String result = template.getForObject(path + "/sample-api/" + key, String.class);
    return result;
  }

  public static void main(String[] args) {
    SpringApplication.run(SampleWireMockApplication.class, args);
  }
}

src/main/resources/application.properties に外部アクセス先のURLをプロパティとして持つ。

server.port=8081

sample.remote.path=http://example.com:12345

テストコードを記述する。

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.Map;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock;

@SpringBootTest(properties = {
    "sample.remote.path=http://localhost:${wiremock.server.port}" }, webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureWireMock(port = 0)
public class ApplicationTest {
  @LocalServerPort
  int serverPort;

  @Autowired
  TestRestTemplate template;

  @Test
  void test() {
    Map<String, String> actual = template.getForObject("http://localhost:" + serverPort + "/sample/key123", Map.class);
    Map<String, String> expected = Map.of("sample-id", "id12345", "sample-value", "value");
    
    assertEquals(expected, actual);
  }
}
  • webEnvironment = WebEnvironment.RANDOM_PORTでこのspring-bootアプリケーションをランダムポートで起動する。
  • @LocalServerPortでそのランダムポート番号を取得できるので、これを使用してapiにアクセスする。
  • @AutoConfigureWireMock(port = 0)でWireMockのauto-configを有効化する。また、port = 0でmock-serverもランダムポートで起動する。
  • ${wiremock.server.port}でmock-serverのランダムポート番号を取得できるので、プロパティファイル内の外部アクセスURLをmock-serverのホスト(localhost)・ランダムポートで上書きする。
  • TestRestTemplateRestTemplateでも構わない。これからはWebTestClientが主流になるかも?
  • assertEqualsはサンプルコードなので簡易にMapでの検証にした。実際にはrecordとかデータ保持クラスに詰め替えたりしてから検証、になると思われる。

次にmock-serverが返す定義ファイルを作成する。デフォルトでは既定のディレクトリにマッピングファイルとレスポンス定義ファイルを作成する。ディレクトリの設定変更方法などは公式ドキュメントを参照。

まずマッピングファイルはsrc/test/resources/mappingsに作成する。以下はsrc/test/resources/mappings/sample-mapping.jsonとして作成した。これの中身はWireMockのドキュメントを参照。

{
  "request": {
    "method": "GET",
    "url": "/sample-api/key123"
  },
  "response": {
    "status": "200",
    "bodyFileName": "sample.json",
    "headers": {
      "Content-Type": "applicaion/json"
    }
  }
}

次にレスポンス定義ファイルはsrc/test/resources/__filesに作成する。以下はsrc/test/resources/__files/sample.jsonとして作成した。上記のマッピングファイルの"bodyFileName": "sample.json"がこのファイルを参照する形になる。

{
  "sample-id": "id12345",
  "sample-value": "value"
}

関連