mockMvcを使用したControllerのテスト方法(Spring Boot)
Controllerのテストやフィルターやインターセプターが実際に動作しているのか検証する際にMockMvcを使用する。 テストの作成方法をメモしておく。
シンプルなControllerのテスト作成
以下がテスト対象のコントローラークラスだとする。
@Controller public class SampleController { @RequestMapping(value = "/", method = RequestMethod.GET) public String forwardStartPage(Model model) { SampleModel sampleModel = new SampleModel(); sampleModel.setId("AA100"); sampleModel.setName("山田"); model.addAttribute("sampleModel", sampleModel); return "index"; } @PostMapping(value = "/test") public String forwardStartPage(@ModelAttribute SampleModel sampleModel) { return "test"; } }
テスト内に登場するモデルは以下。
import lombok.Data; @Data public class SampleModel { private String id; private String name; }
このコントローラーのテストは以下のようになる
class SampleControllerTest { /** {@link MockMvc} */ private MockMvc mockMvc; /** * {@link MockMvc}にテスト対象のコントローラーをセットする。<br> */ @BeforeEach public void beforeEach() { mockMvc = MockMvcBuilders.standaloneSetup(new SampleController()).build(); } @Test void test_01() throws Exception { MvcResult result = mockMvc.perform(get("/")).andExpect(forwardedUrl("index")) .andReturn(); } @Test void test_02() throws Exception { MvcResult result = mockMvc.perform(post("/test").param("id", "BB001").param("name", "鈴木")) .andExpect(forwardedUrl("test")).andReturn(); } }
テストについて順を追って説明する。
1. MockMvcインスタンスの作成
mockMvcのインスタンスは、MockMvcBuildersクラスの standaloneSetup(Object…) メソッドを使用する。
standaloneSetup(Object…)メソッドの引数には使用するControllerのインスタンスを設定する。(複数設定可能)
// Controllerを複数設定する場合 MockMvcBuilders.standaloneSetup(new SampleController(), new MyController()).build();
build()メソッドを呼ぶとmockMvcのインスタンスが生成される。
2. リクエストの実行
作成したmockMvcのインスタンスで perform() メソッドを呼ぶことでリクエストを実行する。
引数にはHTTPメソッドとそのURLを指定する。
mockMvc.perform(get("/")) mockMvc.perform(post("/test")
リクエスト時にパラメーターを指定する場合はparam()
メソッドか、flashAttr()
メソッドを使用する
// param()を使用する場合 mockMvc.perform(post("/test").param("id", "BB001").param("name", "鈴木")) // flashAttr()を使用する場合 SampleModel model = new SampleModel(); model.setId("BB001"); model.setName("鈴木"); mockMvc.perform(post("/test").flashAttr("sampleModel", model))
3. リスエスト結果の検証
andExpect() メソッドで様々な検証を行うことができる。
例えば以下のように記述すると引数に指定したURLにforwardしたかどうかを検証している。
.andExpect(forwardedUrl("index"))
この他にも、status()やmodel()、view()等の検証ができる。 詳しくはこちらを参照。
mockMvc.perform(post("/test").param("id", "BB001").param("name", "鈴木")) .andExpect(forwardedUrl("index")) .andExpect(model().attributeExists("sampleModel")) .andExpect(view().name("index")) .andExpect(status().isOk())
ビルダーの最後に andReturn() メソッドを呼ぶとリクエストの送信結果の入ったMvcResult
が取得できる。
MvcResult result = mockMvc.perform(get("/")).andExpect(forwardedUrl("index")).andReturn();
MvcResult
からModelAndViewのインスタンスや例外処理した例外のインスタンスなどを取得できる。
result.getModelAndView(); result.getResolvedException();
DIを使用したテストを作成する
先ほどのシンプルなテストと、MockMvcの作成方法が異なる。
@SpringBootTest(classes = Config.class) class SampleControllerTest { /** {@link MockMvc} */ private MockMvc mockMvc; @Autowired private WebApplicationContext webApplicationContext; @Configuration public static class Config { @Bean public SampleService sampleService(){ return new SampleServiceImpl(); } } /** * {@link MockMvc}にApplicationContextをセットする。<br> */ @BeforeEach public void beforeEach() { mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); } }
mockMvcにインターセプターやフィルターなどを設定するには
インターセプターを追加する
mockMvcを作成する際に addInterceptors() で追加したいインターセプターのインスタンスを渡す。
@BeforeEach public void beforeEach() { mockMvc = MockMvcBuilders.standaloneSetup(new TestController()) // 追加↓ .addInterceptors(new SampleInterceptor()) // 追加↑ .build(); }
フィルターを追加する
mockMvcを作成する際に addFilter() で第一引数に追加したいフィルターのインスタンス、第二引数に適用するURLのパターンを渡す。
複数のフィルターを設定した場合、先に追加したフィルターが先に動く。
@BeforeEach public void beforeEach() { mockMvc = MockMvcBuilders.standaloneSetup(new TestController()) // 追加↓ .addFilter(new TestFilter(), "/*") // 追加↑ .build(); }
その他MockMvcを作成するためのMockMvcBuildersのメソッドはここを参照。
テスト作成時の注意
フィルターやインターセプターのテストがメインでControllerやModelをテスト用に準備する場合の注意点を記述しておく。
そのテストでしか使用しないControllerやModelは一般的にはテストクラス内に宣言するが、パラメーターをModelにマッピングさせたい場合、 Modelクラスは1つのクラスとして独立する必要があるので注意する(テストクラスの内部クラスだとマッピングが上手くいかない)。
class SampleInterceptorTest { /** {@link MockMvc} */ private MockMvc mockMvc; /** * {@link MockMvc}にテスト対象のコントローラーをセットする。<br> */ @BeforeEach public void beforeEach() { mockMvc = MockMvcBuilders.standaloneSetup(new TestController()).build(); } @Test void test_02() throws Exception { MvcResult result = mockMvc.perform(post("/test").param("id", "BB001").param("name", "鈴木")) .andExpect(forwardedUrl("result")).andReturn(); } @Controller public class TestController{ @PostMapping("/test") public String testForward(@ModelAttribute SampleModel sampleModel) { return "result"; } } // ここにModelを定義してしまうとマッピングが上手くいかない @Data public class SampleModel { private String id; private String name; } }