これは、なにをしたくて書いたもの?
REST Assuredが5.1.0でSpring Boot 3.0に対応したようなので、REST AssuredとSpring Frameworkの組み合わせの確認を含めて
試してみることにしました。
https://github.com/rest-assured/rest-assured/blob/rest-assured-5.3.0/changelog.txt#L90
REST AssuredとSpring Framework
REST AssuredをSpring Frameworkを組み合わせる方法は、MockMvcとWebTestClientの2種類から選ぶことができます。
- Getting Started / "Maven / Gradle Users" / Spring Mock Mvc
- Getting Started / "Maven / Gradle Users" / Spring Web Test Client
それぞれ利用するモジュールが異なり、spring-mock-mvc
とspring-web-test-client
になります。
ドキュメントは、こちらにあります。
Spring · rest-assured/rest-assured Wiki · GitHub
ここで、1度Spring Frameworkのドキュメントを見てみます。
MockMvcにはセットアップ方法がスタンドアロンとSpring Configurationの2種類があります。
Testing / MockMvc / Setup Choices
一方で、REST Assuredのドキュメントにはスタンドアロンでの使用を想定した記載になっています。
WebTestClientの方は、モックサーバーに接続するか、起動しているサーバーに接続しているかを選ぶことができるようです。
Testing / WebTestClient / Setup
REST Assuredのドキュメントは、REST Assured側でWebTestClientを組み上げるAPIの利用方法が記載されています。
Spring Bootを使った場合は、MockMvcはSpring Configuration、WebTestClientもセットアップ済みなので、REST Assuredで使う時もそちらに
習おうと思うのですが、この場合ドキュメントの記載内容とは違ったAPIを使うことになりそうです。
この前提で試してみましょう。
お題は、以下で1度MockMvcとWebTestClientを使ったサンプルを書いているので、こちらを使うことにします。
Spring Web MVCアプリケーションのテスト方法を見ていきたい(+モックでBeanのテスト) - CLOVER🍀
ちょっとしたechoプログラムですが、こちらのテストをREST Assuredを使って書き直します。
環境
今回の環境は、こちら。
$ java --version openjdk 17.0.5 2022-10-18 OpenJDK Runtime Environment (build 17.0.5+8-Ubuntu-2ubuntu120.04) OpenJDK 64-Bit Server VM (build 17.0.5+8-Ubuntu-2ubuntu120.04, mixed mode, sharing) $ mvn --version Apache Maven 3.8.7 (b89d5959fcde851dcb1c8946a785a163f14e1e29) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 17.0.5, vendor: Private Build, runtime: /usr/lib/jvm/java-17-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "5.4.0-135-generic", arch: "amd64", family: "unix"
Spring Bootアプリケーションを作成する
それでは、Spring Bootアプリケーションを作成します。前述のとおり前に書いた以前のエントリーで書いたプログラムを利用しますが、
Spring Bootは3.0を使います。
Spring Initializrでプロジェクトを作成。依存関係にはweb
、webflux
を指定します。
$ curl -s https://start.spring.io/starter.tgz \ -d bootVersion=3.0.1 \ -d javaVersion=17 \ -d type=maven-project \ -d name=spring-boot3-rest-assured-example \ -d groupId=org.littlewings \ -d artifactId=spring-boot3-rest-assured-example \ -d version=0.0.1-SNAPSHOT \ -d packageName=org.littlewings.spring.restassured \ -d dependencies=web,webflux \ -d baseDir=spring-boot3-rest-assured-example | tar zxvf -
webflux
は、WebTestClient
の利用に必要です。MockMvcのみの場合は、REST Assuredを使う場合も含めてwebflux
は必要ありません。
プロジェクト内に移動。
$ cd spring-boot3-rest-assured-example
Mavenでの依存関係など。
<properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
デフォルトで作成されているソースコードは削除しておきます。
$ rm src/main/java/org/littlewings/spring/restassured/SpringBoot3RestAssuredExampleApplication.java src/test/java/org/littlewings/spring/restassured/SpringBoot3RestAssuredExampleApplicationTests.java
では、ソースコードを作成します。
RestController。GETでもPOSTでもメッセージを受け付けて、レスポンスに含めて返すことにします。
src/main/java/org/littlewings/spring/restassured/EchoController.java
package org.littlewings.spring.restassured; import java.util.Optional; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("echo") public class EchoController { MessageDecorationService messageDecorationService; public EchoController(MessageDecorationService messageDecorationService) { this.messageDecorationService = messageDecorationService; } @GetMapping("get") public EchoResponse get(@RequestParam String message) { // Thread.dumpStack(); return EchoResponse .create( messageDecorationService .decorate(Optional.ofNullable(message).orElse("Hello World[get]!!")), Thread.currentThread().getName() ); } @PostMapping("post") public EchoResponse post(@RequestBody EchoRequest echoRequest) { // Thread.dumpStack(); return EchoResponse .create( messageDecorationService .decorate(Optional.ofNullable(echoRequest.getMessage()).orElse("Hello World[post]!!")), Thread.currentThread().getName() ); } }
レスポンス内には、現在のスレッド名を含めます。
リクエストで使うクラス。
src/main/java/org/littlewings/spring/restassured/EchoRequest.java
package org.littlewings.spring.restassured; public class EchoRequest { String message; public static EchoRequest create(String message) { EchoRequest echoRequest = new EchoRequest(); echoRequest.setMessage(message); return echoRequest; } // getter/setterは省略 }
レスポンスで使うクラス。
src/main/java/org/littlewings/spring/restassured/EchoResponse.java
package org.littlewings.spring.restassured; public class EchoResponse { String message; String threadName; public static EchoResponse create(String message, String threadName) { EchoResponse echoResponse = new EchoResponse(); echoResponse.setMessage(message); echoResponse.setThreadName(threadName); return echoResponse; } // getter/setterは省略 }
RestControllerが使うService。リクエストで受け取ったメッセージを少し装飾して返すことにします。
src/main/java/org/littlewings/spring/restassured/MessageDecorationService.java
package org.littlewings.spring.restassured; import org.springframework.stereotype.Service; @Service public class MessageDecorationService { public String decorate(String message) { return String.format("★★★ %s !!★★★", message); } }
main
メソッドを持ったクラス。
src/main/java/org/littlewings/spring/restassured/App.java
package org.littlewings.spring.restassured; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class App { public static void main(String... args) { SpringApplication.run(App.class, args); } }
ここまでで、アプリケーションの準備は完了です。
動作確認しておきましょう。
$ mvn spring-boot:run
こんな感じになります。
## GET $ curl localhost:8080/echo/get?message=Hello {"message":"★★★ Hello !!★★★","threadName":"http-nio-8080-exec-1"} ## POST $ curl -H 'Content-Type: application/json' localhost:8080/echo/post -d '{"message": "Hello"}' {"message":"★★★ Hello !!★★★","threadName":"http-nio-8080-exec-2"}
REST Assuredを使ってテストをする
それでは、このサンプルアプリケーションに対してREST Assuredを使ってテストを書いていきます。
REST Assured+MockMvc
まずは、REST AssuredとMockMvcを組み合わせます。
Maven依存関係に、spring-mock-mvc
を追加。
<dependency> <groupId>io.rest-assured</groupId> <artifactId>spring-mock-mvc</artifactId> <version>5.3.0</version> <scope>test</scope> </dependency>
spring-mock-mvc
に関する記述は、こちら。
Spring Support / Spring Mock Mvc Module
作成したテストコードは、こちら。
src/test/java/org/littlewings/spring/restassured/MockMvcWithRestAssuredTest.java
package org.littlewings.spring.restassured; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import static io.restassured.module.mockmvc.RestAssuredMockMvc.given; import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @SpringBootTest @AutoConfigureMockMvc public class MockMvcWithRestAssuredTest { @Autowired MockMvc mvc; @Test public void getTest() { given() .queryParam("message", "MockMvc[get]") .mockMvc(mvc) // MockMvc .when() .get("/echo/get") .then() .assertThat(status().isOk()) // MockMvcResultMatchers .body("message", is("★★★ MockMvc[get] !!★★★")) .body("threadName", is("main")); } @Test public void postTest() { given() .contentType(MediaType.APPLICATION_JSON) .body(EchoRequest.create("MockMvc[post]")) .mockMvc(mvc) // MockMvc .when() .post("/echo/post") .then() .assertThat(status().isOk()) // MockMvcResultMatchers .header("content-type", MediaType.APPLICATION_JSON_VALUE) .body("message", is("★★★ MockMvc[post] !!★★★")) .body("threadName", is("main")); } }
ポイントをいくつか。
まず、MockMvc
を使うので@AutoConfigureMockMvc
アノテーションを付与します。
@SpringBootTest @AutoConfigureMockMvc public class MockMvcWithRestAssuredTest {
テストコードの方は、パッと見は通常のREST Assuredを使ったコードです。MockMvc
は直接操作しません。
@Test public void getTest() { given() .queryParam("message", "MockMvc[get]") .mockMvc(mvc) // MockMvc .when() .get("/echo/get") .then() .assertThat(status().isOk()) .body("message", is("★★★ MockMvc[get] !!★★★")) .body("threadName", is("main")); }
import
文を見ると、使っているのはRestAssuredMockMvc
になっています。
import static io.restassured.module.mockmvc.RestAssuredMockMvc.given;
また、MockMvcRequestSpecification#mockMvc
でMockMvc
のインスタンスを設定することも必要です。
given() .queryParam("message", "MockMvc[get]") .mockMvc(mvc) // MockMvc
REST Assuredのドキュメントの場合、この部分がスタンドアロンでの利用想定になっています。
Spring Support / Spring Mock Mvc Module / Bootstrapping RestAssuredMockMvc
具体的には、MockMvcRequestSpecification#standaloneSetup
を使うように書かれています。
given().standaloneSetup(new GreetingController()). ..
今回はSpring Bootで設定済みのMockMvc
を使うため、MockMvcRequestSpecification#mockMvc
でMockMvc
のインスタンスを指定することに
なります。
また、ValidatableMockMvcResponse#assertThat
でMockMvcResultMatchers
のメソッドも使うことができます。
Spring Support / Spring Mock Mvc Module / Using Result Matchers
RestControllerを動かしているスレッドはmain
スレッドなので、モック環境でテストを行っていることも確認できます。
.body("threadName", is("main"));
REST AssuredとMockMvcの組み合わせは、とりあえずこんなところでしょうか。
もう少し詳しい使い方は、ドキュメントを参照。
Spring Support / Spring Mock Mvc Module
REST Assured+WebTestClient
次は、REST AssuredとWebTestClientを組み合わせます。
Maven依存関係に、spring-web-test-client
を追加。
<dependency> <groupId>io.rest-assured</groupId> <artifactId>spring-web-test-client</artifactId> <version>5.3.0</version> <scope>test</scope> </dependency>
spring-web-test-client
に関するドキュメントは、こちらです。
Spring Support / Spring Web Test Client Module
ドキュメントはリアクティブなControllerを対象にして書かれていますが、ふつうのSpring Web MVCで書かれたControllerでも構いません。
WebTestClient
がそういうものですからね。
作成したテストコードは、こちら。
src/test/java/org/littlewings/spring/restassured/WebTestClientWithRestAssuredTest.java
package org.littlewings.spring.restassured; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.web.reactive.server.WebTestClient; import static io.restassured.module.webtestclient.RestAssuredWebTestClient.given; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.startsWith; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class WebTestClientWithRestAssuredTest { @Autowired WebTestClient webClient; @Test public void getTest() { given() .queryParam("message", "WebEnvironment Test[get]") .webTestClient(webClient) // WebTestClient .when() .get("/echo/get") .then() .statusCode(200) .body("message", is("★★★ WebEnvironment Test[get] !!★★★")) .body("threadName", startsWith("http-nio-auto-1-exec-")); } @Test public void postTest() { given() .contentType(MediaType.APPLICATION_JSON) .body(EchoRequest.create("WebEnvironment Test[post]")) .webTestClient(webClient) // WebTestClient .when() .post("/echo/post") .then() .statusCode(200) .header("content-type", MediaType.APPLICATION_JSON_VALUE) .body("message", is("★★★ WebEnvironment Test[post] !!★★★")) .body("threadName", startsWith("http-nio-auto-1-exec-")); } }
こちらもポイントをいくつか。
テストは組み込みサーバーを使って行うため、SpringBootTest
のwebEnvironment
にRANDOM_PORT
またはDEFINED_PORT
を指定します。
今回はRANDOM_PORT
を指定しました。
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class WebTestClientWithRestAssuredTest {
こちらもパッと見はふつうのREST Assuredを使ったテストに見えます。WebTestClient
自体は直接操作しません。
@Test public void getTest() { given() .queryParam("message", "WebEnvironment Test[get]") .webTestClient(webClient) // WebTestClient .when() .get("/echo/get") .then() .statusCode(200) .body("message", is("★★★ WebEnvironment Test[get] !!★★★")) .body("threadName", startsWith("http-nio-auto-1-exec-")); }
ここでimport
文を見ると、使っているのはRestAssuredWebTestClient
になっています。
import static io.restassured.module.webtestclient.RestAssuredWebTestClient.given;
また、WebTestClientRequestSpecification#webTestClient
でWebTestClient
のインスタンスを設定することも必要です。
given() .queryParam("message", "WebEnvironment Test[get]") .webTestClient(webClient) // WebTestClient
MockMvcの時と同様に、WebTestClient向けのモジュールの場合もこの部分がスタンドアロン向けのドキュメントの記載になっています。
Spring Support / Spring Web Test Client Module / Bootstrapping RestAssuredWebTestClient
こちらもWebTestClientRequestSpecification#standaloneSetup
を使うように書かれています。
given().standaloneSetup(new GreetingController()). ..
今回はSpring Bootで設定済みのWebTestClient
を使うため、WebTestClientRequestSpecification#webTestClient
でWebTestClient
の
インスタンスを指定することになります。
その他は、通常のREST Assuredを使った書き方と変わりません。
レスポンスに含まれているスレッド名もTomcatのものになっているので、組み込みTomcatを使って動作していることがわかります。
.body("threadName", startsWith("http-nio-auto-1-exec-"));
REST AssuredとWebTestClientの組み合わせも、こんなところでしょう。
ドキュメントの記述量は、MockMvc向けのモジュールと比べて多くありません。
Spring Support / Spring Web Test Client Module
まとめ
REST AssuredがSpring Boot 3.0に対応したようなので、REST AssuredのSpring向けのモジュールと合わせて試してみました。
MockMvc
やWebTestClient
の存在をあまり意識せず、REST Assuredの書き方で書けるのが良いですね。