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 は method と path から MockResponse を組み立て、説明にある順番で判定すること
- GET /users/ に続く部分を id として取り出し、1 のときだけ 200 と alice を返すこと
- perform はステータスと本文の両方が期待と一致したときだけ pass を返し、それ以外は fail を返すこと
入出力例
perform("GET", "/users", 200, "list") → "pass"
perform("POST", "/users", 201, "created") → "pass"
perform("GET", "/users/1", 200, "alice") → "pass"
perform("GET", "/users/9", 404, "not found") → "pass"
perform("DELETE", "/users", 405, "method not allowed") → "pass"
perform("GET", "/health", 404, "not found") → "pass"
perform("GET", "/users", 200, "users") → "fail"
perform("POST", "/users", 200, "created") → "fail"