JUnit

JUnit5のコントローラのテストで@WebMvcTestや@Importのアノテーションを利用してみた

コントローラクラスのテストを行う際、@SpringBootTestアノテーションの代わりに@WebMvcTestアノテーションを利用すると、@WebMvcTestアノテーションで指定したコントローラクラスと、コントローラクラスを動作するために必要なMVCインフラストラクチャのみをインスタンス化することができるため、テストクラスの実行速度を早めることができる。

今回は、@WebMvcTestアノテーションを利用してコントローラクラスのテストを行ってみたので、そのサンプルプログラムを共有する。

なお、@WebMvcTestアノテーションを利用する際は、@Importアノテーションを利用して、コントローラクラスで参照するクラスをDIできる指定を追加する必要がある。

前提条件

下記記事の実装が完了していること。

JUnit5でBean Validationのテストを行ってみたSpring Bootを利用したWebアプリケーションで、Formオブジェクトのチェック処理を行う際はBean Validationを利...

作成したサンプルプログラムの内容

作成したサンプルプログラムの構成は以下の通り。
サンプルプログラムの構成
なお、上記の赤枠は、前提条件のプログラムから変更したプログラムである。

コントローラクラスのテストを行うクラスは以下の通りで、@WebMvcTestアノテーション・@Importアノテーションをクラスに付与している。

package com.example.demo;

import static org.springframework.test.web.client.match.MockRestRequestMatchers.content;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.header;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;

import java.util.ArrayList;
import java.util.List;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.web.client.RestTemplate;

import com.fasterxml.jackson.databind.ObjectMapper;

// @SpringBootTestアノテーションの代わりに、@WebMvcTestアノテーションを利用
// @Importアノテーションで、テスト対象クラスで参照するクラスをDIできるように指定
@WebMvcTest(controllers = DemoController.class)
@Import({RestTemplate.class, HttpHeaders.class})
public class DemoControllerTest {

    /** MockMvcオブジェクト */
    // @WebMvcTestアノテーションにより、このアノテーションの属性に指定したコントローラクラスを
    // テスト対象クラスに設定したMockMvcオブジェクトが、自動生成される
    @Autowired
    private MockMvc mockMvc;

    /** MockServerオブジェクト */
    private MockRestServiceServer mockServer;

    /** テスト対象となるコントローラクラスで呼ばれるRestTemplateオブジェクト */
    @Autowired
    private RestTemplate restTemplate;

    /** JSON文字列とObjectの変換を行うオブジェクト */
    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 各テストメソッドを実行する前に実行する処理
     */
    @BeforeEach
    public void setUp() {
        // テスト対象となるコントローラクラスで呼ばれるRestTemplateにMockサーバーを割り当てる
        mockServer = MockRestServiceServer
                .bindTo(restTemplate)
                .build();
    }
    
    /**
     * DemoControllerクラスのindexメソッドのテストを実行する
     * @throws Exception 任意例外
     */
    @Test
    public void testIndex() throws Exception {
        String mockServerRes = objectMapper.writeValueAsString(getUserDataList());
        List<UserData> userDataList = getUserDataList();

        System.out.println("*** testIndexメソッド 開始 ***");

        // テスト対象となるコントローラクラスのindexメソッドで呼ばれるAPI実行時のモック設定をする
        // APIのURLを設定
        mockServer.expect(requestTo("http://localhost:8085/users"))
                // HTTPメソッドをGETに設定
                .andExpect(method(HttpMethod.GET))
                // APIの戻り値を設定
                .andRespond(withSuccess(mockServerRes, MediaType.APPLICATION_JSON));

        System.out.println("API実行後の戻り値 : " + mockServerRes);

        // テスト対象メソッド(index)を実行
        mockMvc.perform(get("/"))
                // HTTPステータスがOKであることを確認
                .andExpect(status().isOk())
                // 次画面の遷移先がindex.htmlであることを確認
                .andExpect(view().name("index"))
                // Modelオブジェクトにエラーが無いことを確認
                .andExpect(model().hasNoErrors())
                // Modelオブジェクトの設定値を確認
                .andExpect(model().attribute("userDataList", userDataList));

        System.out.println("Modelオブジェクト、userDataListの設定値 : " + userDataList);
        System.out.println("*** testIndexメソッド 終了 ***");
    }
    
    /**
     * testIndexメソッドで利用するユーザーデータリストを返却する
     * @return ユーザーデータリスト
     */
    private List<UserData> getUserDataList() {
        List<UserData> userDataList = new ArrayList<>();
        userDataList.add(
            new UserData(1, "テスト プリン1", 2012, 3, 12, "1", "テスト1"));
        userDataList.add(
            new UserData(2, "テスト プリン2", 2013, 1, 7, "2", "テスト2"));
        return userDataList;
    }

    /**
     * DemoControllerクラスのaddメソッド(正常時)のテストを実行する
     * @throws Exception 任意例外
     */
    @Test
    public void testAddNormal() throws Exception {
        UserData userData 
            = new UserData(3, "テスト プリン3", 2010, 8, 31, "2", "テスト3");
        String jsonUserData = objectMapper.writeValueAsString(userData);

        System.out.println("*** testAddNormalメソッド 開始 ***");

        // テスト対象となるコントローラクラスのaddメソッドで呼ばれるAPI実行時のモック設定をする
        mockServer.expect(requestTo("http://localhost:8085/users"))
                // HTTPメソッドをPOSTに設定
                .andExpect(method(HttpMethod.POST))
                // HTTP HeaderのContent-Typeがapplication/jsonであることを確認
                .andExpect(header("Content-Type", "application/json"))
                // リクエストボディの設定値を確認
                .andExpect(content().string(jsonUserData))
                // APIの戻り値を設定
                .andRespond(withSuccess(jsonUserData, MediaType.APPLICATION_JSON));

        System.out.println("API実行時のリクエストボディ : " + jsonUserData);
        System.out.println("API実行後の戻り値 : " + jsonUserData);

        String mockServerRes = objectMapper.writeValueAsString(getUserDataList2());
        List<UserData> userDataList2 = getUserDataList2();

        // テスト対象となるコントローラクラスのindexメソッドで呼ばれるAPI実行時のモック設定をする
        mockServer.expect(requestTo("http://localhost:8085/users"))
                // HTTPメソッドをGETに設定
                .andExpect(method(HttpMethod.GET))
                // APIの戻り値を設定
                .andRespond(withSuccess(mockServerRes, MediaType.APPLICATION_JSON));

        // テスト対象メソッド(add)を実行
        DemoForm demoForm 
            = new DemoForm("3", "テスト プリン3", "2010", "8", "31", "2", "テスト3");
        mockMvc.perform(MockMvcRequestBuilders.post("/add")
                // paramsに設定する値を指定
                .param("next", "next")
                // formオブジェクトを設定
                .flashAttr("demoForm", demoForm))
                // HTTPステータスがOKであることを確認
                .andExpect(status().isOk())
                // 次画面の遷移先がindex.htmlであることを確認
                .andExpect(view().name("index"))
                // Modelオブジェクトにエラーが無いことを確認
                .andExpect(model().hasNoErrors())
                // Modelオブジェクトの設定値を確認
                .andExpect(model().attribute("userDataList", userDataList2));

        System.out.println("formオブジェクトの設定値 : " + demoForm);
        System.out.println("Modelオブジェクト、userDataListの設定値 : " + userDataList2);
        System.out.println("*** testAddNormalメソッド 終了 ***");
    }
    
    /**
     * testAddNormalメソッドで利用するユーザーデータリストを返却する
     * @return ユーザーデータリスト
     */
    private List<UserData> getUserDataList2() {
        List<UserData> userDataList = new ArrayList<>();
        userDataList.add(
            new UserData(1, "テスト プリン1", 2012, 3, 12, "1", "テスト1"));
        userDataList.add(
            new UserData(2, "テスト プリン2", 2013, 1, 7, "2", "テスト2"));
        userDataList.add(
            new UserData(3, "テスト プリン3", 2010, 8, 31, "2", "テスト3"));
        return userDataList;
    }

    /**
     * DemoControllerクラスのaddメソッド(異常時)のテストを実行する
     * @throws Exception 任意例外
     */
    @Test
    public void testAddException() throws Exception {
        System.out.println("*** testAddExceptionメソッド 開始 ***");
        
        // テスト対象メソッド(add)を実行
        DemoForm demoForm = new DemoForm("3", "テスト プリン3"
                , "2010", "2", "31", "2", "テスト3");
        
        mockMvc.perform(MockMvcRequestBuilders.post("/add")
                // paramsに設定する値を指定
                .param("next", "next")
                // formオブジェクトを設定
                .flashAttr("demoForm", demoForm))
                // HTTPステータスがOKであることを確認
                .andExpect(status().isOk())
                // 次画面の遷移先がadd.htmlであることを確認
                .andExpect(view().name("add"))
                // Modelオブジェクトにチェックエラー内容が設定されたことを確認
                .andExpect(model().hasErrors())
                .andExpect(model().attributeHasFieldErrorCode(
                        "demoForm", "birthY", "CheckDate"));

        System.out.println("formオブジェクトの設定値 : " + demoForm);
        System.out.println("*** testAddExceptionメソッド 終了 ***");
    }

}

その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/java/tree/master/junit5-webmvctest-import/demoRestApiCallWeb



「AOMEI Partition Assistant Standard(無料)版」は便利なパーティション管理ツールだったハードディスクの記憶領域を論理的に分割し、分割された個々の領域のことを、パーティションといいます。 例えば、以下の図の場合、C/D...

テストプログラムの実行結果

テストプログラムの実行結果は、以下の通り。

1) testIndexメソッドを実行した結果は以下の通りで、Spring Bootアプリケーションを起動後、テスト対象のコントローラクラスのindexメソッドを実行し正常終了していることが確認できる。
サンプルプログラムの実行結果_1

2) testAddメソッドを実行した結果は以下の通りで、Spring Bootアプリケーションを起動後、テスト対象のコントローラクラスのaddメソッドを実行し正常終了していることが確認できる。
サンプルプログラムの実行結果_2

3) testAddExceptionメソッドを実行した結果は以下の通りで、Spring Bootアプリケーションを起動後、テスト対象のコントローラクラスのaddメソッドを実行しチェックエラーとなる動作が確認できる。
サンプルプログラムの実行結果_3

要点まとめ

  • コントローラクラスのテストを行う際、@SpringBootTestアノテーションの代わりに@WebMvcTestアノテーションを利用すると、テストクラスの実行速度を早めることができる。