spring-bootで作成するweb-apiのintegration test(以下IT)を記述するのにWebTestClient
が使える。これは内部的にWebClient
を使用する。同様にTestRestTemplate
は内部的にRestTemplate
を使用する。環境に応じて使い分けになる。今回はWebTestClient
によるITの記述について。
ドキュメントは WebTestClient :: Spring Docsにあり、ここ読めばだいたいは困らないと思う。
ソースコード
build.gradle
plugins { id 'org.springframework.boot' version '2.6.7' 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() } 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.boot:spring-boot-starter-webflux' } tasks.named('test') { useJUnitPlatform() }
注意点として、WebTestClient
のinterfaceはspring-testにいるがWebClient
の実クラスがないと実行時エラーになる。ので、依存性にwebfluxが必要になる。webfluxの依存性はtestImplementation
にしているがこれは従来のwebでのITを想定してるため。webfluxを使うならtestにする必要はない。
controller
まず適当なcontrollerを作る。
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @SpringBootApplication() public class SampleWebTestClientApplication { public record SampleResponse(String first, String second, List<Integer> list) { } @RequestMapping("/sample") public SampleResponse sample() { return new SampleResponse("f", "s", List.of(1, 2, 3)); } public static void main(String[] args) { SpringApplication.run(SampleWebTestClientApplication.class, args); } }
test
テストクラスを作る。
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.web.server.LocalServerPort; import org.springframework.test.web.reactive.server.WebTestClient; @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) public class ApplicationTest { @LocalServerPort int port; @Autowired WebTestClient client; @Test void test() { String json = """ { "second":"s", "first":"f" , "list":[3,2,1] } """; client.get().uri("/sample") .exchange() .expectStatus().isOk() .expectBody().json(json); } }
@SpringBootTest
は今まで通りのapiを実際に起動してのテストを行うためのアノテーション。WebTestClient
は@SpringBootTest
によって自動的にbeanが作られる。どうゆうbeanが作られるかはここを参照。色々設定してなければ、WebTestClient.bindToServer().baseUrl("http://localhost:" + port).build()
とおおむね同様と見てよい。- apiにアクセスしてレスポンスのjson文字列同士を比較している。javaのクラスにdeserializeしてから比較しても良いと思う。ただ、デフォルトで割と柔軟なjsonチェックをしてくれるので、ITとしてはこれで良いんじゃないかな、とか思う。
- 柔軟なチェックの概要はソースコードにもある通り、空白や改行・要素順序・配列の要素順序、を無視してくれる。また、expect側に要素が欠けててもokになる(ex
{"second":"s"}
でもok)。strictなチェックをしたい場合は.expectBody().json(json, true)
にする*1。 WebTestClient
に色々あるassertでは不足していてカスタマイズしたい場合はconsumeWith
を使う。以下はjson(expect)
とほぼ同等の挙動になる。
client.get().uri("/sample") .exchange() .expectStatus().isOk() .expectBody(String.class) .consumeWith(result -> { try { new JsonExpectationsHelper().assertJsonEqual(json, result.getResponseBody(), false); } catch (Exception e) { e.printStackTrace(); } });
*1:5.3.16以降で指定可能