mockMvcを使用したControllerのテスト方法(Spring Boot)

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;
    }
}