コントローラクラスのテストを行う際、@SpringBootTestアノテーションの代わりに@WebMvcTestアノテーションを利用すると、@WebMvcTestアノテーションで指定したコントローラクラスと、コントローラクラスを動作するために必要なMVCインフラストラクチャのみをインスタンス化することができるため、テストクラスの実行速度を早めることができる。
今回は、@WebMvcTestアノテーションを利用してコントローラクラスのテストを行ってみたので、そのサンプルプログラムを共有する。
なお、@WebMvcTestアノテーションを利用する際は、@Importアノテーションを利用して、コントローラクラスで参照するクラスをDIできる指定を追加する必要がある。
前提条件
下記記事の実装が完了していること。
作成したサンプルプログラムの内容
作成したサンプルプログラムの構成は以下の通り。
なお、上記の赤枠は、前提条件のプログラムから変更したプログラムである。
コントローラクラスのテストを行うクラスは以下の通りで、@WebMvcTestアノテーション・@Importアノテーションをクラスに付与している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | 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
テストプログラムの実行結果
テストプログラムの実行結果は、以下の通り。
1) testIndexメソッドを実行した結果は以下の通りで、Spring Bootアプリケーションを起動後、テスト対象のコントローラクラスのindexメソッドを実行し正常終了していることが確認できる。
2) testAddメソッドを実行した結果は以下の通りで、Spring Bootアプリケーションを起動後、テスト対象のコントローラクラスのaddメソッドを実行し正常終了していることが確認できる。
3) testAddExceptionメソッドを実行した結果は以下の通りで、Spring Bootアプリケーションを起動後、テスト対象のコントローラクラスのaddメソッドを実行しチェックエラーとなる動作が確認できる。
要点まとめ
- コントローラクラスのテストを行う際、@SpringBootTestアノテーションの代わりに@WebMvcTestアノテーションを利用すると、テストクラスの実行速度を早めることができる。