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)・ランダムポートで上書きする。TestRestTemplate
はRestTemplate
でも構わない。これからは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" }