MockMvcでエンドポイントを試す
このレッスンでは、MockMvc が何をしているのかを理解し、リクエストと期待する結果を突き合わせる判定を自分で書けるようになります。
実サーバーを立てずにディスパッチャを通します
コントローラのテストで困るのは、HTTP を通すと遅くなり、通さないと本物と違うものを確かめてしまうことです。MockMvc はその真ん中を取ります。Tomcat のような実サーバーは起動せず、しかし DispatcherServlet には本当に通すという仕組みです。
このコースの序盤で見たとおり、Spring MVC のリクエスト処理はディスパッチャが中心にいて、そこからルーティング表の照合、パス変数の抽出、本文の JSON からオブジェクトへの変換、検証、ハンドラの呼び出し、戻り値の JSON への変換、という順で進みます。MockMvc はこの一連の流れをそのまま実行します。省略されるのはソケットの部分だけです。だから @PathVariable の取り出しミスも @Valid の付け忘れも、MockMvc でちゃんと見つかります。
perform と andExpect
書き方は決まった形をしています。
Java
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired MockMvc mockMvc;
@MockBean UserService userService;
@Test
void 一覧は200を返す() throws Exception {
mockMvc.perform(get("/users"))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].name").value("alice"));
}
}perform にリクエストを渡して実行し、andExpect に期待することを並べます。andExpect はいくつでも重ねられ、ひとつでも満たされなければテストが失敗します。
リクエストの組み立てには get、post、put、delete を使います。本文やヘッダを付けたい場合は続けて指定します。
Java
mockMvc.perform(post("/users")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"name\":\"alice\"}"))
.andExpect(status().isCreated());contentType を忘れると、Spring は本文をどう解釈すればよいか分からず 415 を返します。POST のテストが 415 で落ちるときは、たいていこれです。
期待の書き方
よく使う andExpect の中身は次の通りです。
| 書き方 | 確かめること |
|---|---|
status().isOk() | ステータスが 200 |
status().isCreated() | ステータスが 201 |
status().isNotFound() | ステータスが 404 |
status().isBadRequest() | ステータスが 400 |
jsonPath("$.name").value("alice") | JSON の該当項目の値 |
content().string("ok") | 本文が文字列として一致 |
header().string("Location", "/users/1") | ヘッダの値 |
jsonPath は JSON の中の位置を式で指す書き方です。$ が根で、$.name が最上位の name、$[0].name が配列の先頭要素の name を指します。本文全体を文字列として比較すると項目の順番が変わっただけで落ちるので、JSON を返すエンドポイントでは jsonPath のほうが壊れにくくなります。
デバッグに効く一行
期待が外れたのに理由が分からないときは、andDo(print()) を挟むと実際のリクエストとレスポンスの全体が出力されます。ステータスもヘッダも本文も見えるので、推測でアサーションを書き換える前にこれを使ってください。
演習で書くこと
MockMvc がやっている「ディスパッチャを通して、返ってきたものを期待と突き合わせる」という2段階を確かめます。簡易ディスパッチャは用意済みで、その結果をステータスと本文の両面で期待と比べ、pass か fail を返す判定を作ります。andExpect を重ねたときの「ひとつでも外れたら失敗」という性質も、そのまま再現します。
演習で使うひな形
dispatch("GET", "/users") の結果は res.status() が200、res.body() が "list" です。期待するステータスが200でも、期待する本文が "users" なら fail です。ルーティングはひな形に任せ、perform では両方の期待値を比較する判断を書きます。
要件
- dispatch を呼んで MockResponse を受け取ること
- status() を expectedStatus と比較すること
- body() を expectedBody と equals で比較し、両方一致の場合だけ pass を返すこと