Spring5.0 + KotlinのRouterFunctionのテストはどうすればよいか? 試してみた
引き続きSpring5.0(Spring Boot 2.0) + KotlinのWebアプリケーションを試している。今回はRouterFunction
をつかってHTTPルーティングを定義したときにテストコードはどう書くのか?気になったのでまとめてみた。
Router Function
Router FunctionはSpring5.0から導入されるものでアノテーションベースのHTTPサービスの定義ではなく RouterFunctionDsl
クラスを実装していくことになる。エンドポイントのURLとHTTP Handlerをマッピングを行うことでルーティングが定義できる。
@Configuration class TaskRoutes(private val taskHandler: TaskHandler, private val exceptionFilter: ExceptionFilter) { @Bean fun taskRouter() = router { (accept(APPLICATION_JSON) and "/api").nest { "/task".nest { POST("/", taskHandler::create) GET("/{id}", taskHandler::fetchByTaskId) PUT("/{id}", taskHandler::updateByTaskId) DELETE("/{id}", taskHandler::deleteByTaskId) PUT("/{id}/finish", taskHandler::finishByTaskId) } "/tasks".nest { GET("/", taskHandler::fetchAll) } } }.filter(exceptionFilter()) }
次からはRouter Functionで定義されたHTTPサービスのテストコードを書いていきたい。
MockMvcではなくWebTestClient をつかう
SpringのコントローラテストコードはMockMvcを用いて書くことが多かったがRouter Functionでは WebTestClient
をつかう。
上記の taskRouter
はタスクエンティティを扱うコントローラでありルーティングも固有のBeanで定義している。また TaskHandler
はDBアクセスやgRPC サーバなどロジックが絡むのでモック化していきたい。そのような場合にはWebTestClientに用意されている bindToRouterFunction
を定義する。
@RunWith(SpringRunner::class) class TaskRoutesTest { lateinit var client : WebTestClient lateinit var taskHandler: TaskHandler lateinit var exceptionFilter: ExceptionFilter val mapper = ObjectMapper().registerModule(KotlinModule()) @Before fun before() { taskHandler = mock(TaskHandler::class) exceptionFilter = ExceptionFilter() val taskRoutes = TaskRoutes(taskHandler, exceptionFilter) client = WebTestClient.bindToRouterFunction(taskRoutes.taskRouter()).build() } ・・・ }
TaskHandler
をモック化する(taskHandler変数を定義)- taskHandler変数とexceptionFilter変数から
TaskRoutes
のコンストラクタを呼び出しRoutesオブジェクトを生成する(taskRoutes変数を定義) - taskRoutes変数を
bindToRouterFunction
に渡しWebTestClientオブジェクトを生成する
以上をbeforeに定義してテスト実行の準備を整える
@Testを定義する
タスクを1件取得するエンドポイントURLのテストコードは次のようになった。
@Test fun `GET Task`() { // mock `when`(taskHandler.fetchByTaskId(any())).thenReturn(ok().json().body(Mono.just(mockModel))) client.get().uri("/api/task/1") .accept(MediaType.APPLICATION_JSON_UTF8) .exchange() .expectStatus().isOk .expectBody() .consumeAsStringWith { val actual = mapper.readValue<TaskModel>(it, TaskModel::class) actual.id shouldBe 1L actual.title shouldBe "task title" actual.createdAt shouldBe "2017-06-13T16:22:52Z" actual.updatedAt shouldBe "2017-06-13T16:22:52Z" } }
taskHandler.fetchByTaskId
をモック化して返すオブジェクトを定義しているexchange()
でResponseSpec
オブジェクトが返ってくるのでHTTPステータスやレスポンスボディを取得しアサーションする
Spring REST Docsは使えるの?
Test DrivenにAPIドキュメントを生成するSpring Rest Docs
はMockMVCベースなので今のところは対応していない。
対応はしていないが対応はする予定である。issueから確認できるがREST Docs 2.0で対応予定のようなので期待したい。
同じくAPIドキュメントをアノテーションベースで生成できる便利なSpring Foxもサポート予定であることをissueから観測した。
Spring 5 support · Issue #1773 · springfox/springfox · GitHub
個人的にはエンドポイント設計時からアノテーションを付与すればAPI仕様書がつくれるSpring Foxのほうが好みである。だがアノテーションベースで定義する手法が、HTTPルーティングをアノテーションベースで定義しないRouter Functionにどのようにマッピングされるのだろうか。サポート結果が楽しみである。
まとめ
- RouterFunctionをつかった場合でもテストコードはモックも交えることができるし問題無さそうで導入に前向きになれた。
- APIドキュメントのライブラリのサポート体制状況も観測できたので、Spring5.0のリリースが待ち遠しい。
コード
コードを書きながら調べたのでgithubと合わせて確認いただけます。
テストコードはこちらです。 spring5-kotlin-application/TaskRoutesTest.kt at master · nsoushi/spring5-kotlin-application · GitHub