はじめに
spring boot2でユニットテストを書くことになったので、
書き方を整理しようと思います。
Modelにセットした値のテストってどう書くんだっけ?
といったケースで思い出しように使ってもらえたらと思います。
サンプルコードはGitHubにあげてます。
レスポンスStatusのテスト
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
this.mockMvc.perform(get("/")).andDo(print())
// ステータス「200 OK」を返すかどうか
.andExpect(status().isOk())
.andExpect(status().is(200))
ドキュメントはこちらのようです。
isOk()でも、is(200)でも、どちらでも同じ結果になります。
thymeleafのテンプレートファイル
@Controller
@EnableAutoConfiguration
public class HomeController {
@GetMapping(path = "/")
String home(HttpServletRequest request, Model model) {
// home.htmlを指定する
return "home";
}
}
といった感じで、thymeleafのテンプレートファイルをreturnしますよね。
これのテストです。
// テスト
this.mockMvc.perform(get("/")).andDo(print())
// テンプレート「home」を返すかどうか
.andExpect(view().name("home"))
nameメソッドでテストします。
ドキュメントはこちらです。
Modelのテスト
コントローラーでModelにデータを詰めますよね。
public class MyData {
private String strData;
private int intData;
public MyData(String strData, int intData) {
this.strData = strData;
this.intData = intData;
}
public void setStrData(String strData) {
this.strData = strData;
}
public String getStrData() {
return this.strData;
}
public void setIntData(int intData) {
this.intData = intData;
}
public int getIntData() {
return this.intData;
}
}
@Controller
@EnableAutoConfiguration
public class HomeController {
@GetMapping(path = "/")
String home(HttpServletRequest request, Model model) {
// string
model.addAttribute("test", "this is test");
// HashMap<String, String>
HashMap<String, String> map = new HashMap<String, String>();
map.put("name", "momotaro");
map.put("age", "23");
model.addAttribute("map", map);
// List<String>
List<String> list = new ArrayList<String>();
list.add("list1");
list.add("list2");
list.add("list3");
model.addAttribute("list", list);
// List<MyData>
List<MyData> list2 = new ArrayList<MyData>();
list2.add(new MyData("test1", 111));
list2.add(new MyData("test2", 222));
model.addAttribute("list2", list2);
// home.htmlを指定する
return "home";
}
}
@SpringBootTest(classes = HomeController.class)
@AutoConfigureMockMvc
public class HomeControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
void home画面() throws Exception {
HashMap<String, String> map = new HashMap<String, String>();
map.put("name", "momotaro");
map.put("age", "23");
List<String> list = new ArrayList<String>();
list.add("list1");
list.add("list2");
list.add("list3");
List<MyData> list2 = new ArrayList<MyData>();
list2.add(new MyData("test1", 111));
list2.add(new MyData("test2", 222));
// テスト
this.mockMvc.perform(get("/")).andDo(print())
// ステータス「200 OK」を返すかどうか
.andExpect(status().isOk())
.andExpect(status().is(200))
// テンプレート「home」を返すかどうか
.andExpect(view().name("home"))
// Modelのテスト。
// string型
.andExpect(model().attribute("test", "this is test"))
// HashMap型
.andExpect(model().attribute("map", map))
// List<String>型
.andExpect(model().attribute("list", hasSize(3))) // リストのサイズ
.andExpect(model().attribute("list", hasItem("list1"))) // list1が含まれているか
.andExpect(model().attribute("list", hasItem("list2"))) // list2が含まれているか
.andExpect(model().attribute("list", hasItem("list3"))) // list3が含まれているか
.andExpect(model().attribute("list", contains("list1", "list2", "list3"))) // list1, list2, list3の順で含まれているか
.andExpect(model().attribute("list", is(list))) // listと一致しているかどうか
// List<MyData>型
.andExpect(model().attribute("list2", hasSize(2))) // リストのサイズ
.andExpect(model().attribute("list2",
hasItem(allOf(hasProperty("strData", is("test1")), hasProperty("intData", is(111))))
)) // この組み合わせのデータが含まれているか
.andExpect(model().attribute("list2",
hasItem(allOf(hasProperty("strData", is("test2")), hasProperty("intData", is(222))))
)) // この組み合わせのデータが含まれているか
//.andExpect(model().attribute("list2", is(list2))) // list2と一致しているかどうか -> この書き方は出来ない。エラーになる。
;
}
}
このようにattributeメソッドでテストできます。
ドキュメントはこちらです
※Listはis()で判定できましたが、Listはis()で判定できないようです。
redirectのテスト
POSTの処理でredirectしますよね。
@PostMapping(path = "/testpost")
public ModelAndView testpost(RedirectAttributes redirectAttributes, @RequestParam(value = "intvalue", required = false, defaultValue = "0") Integer intvalue) {
ModelAndView modelAndView = new ModelAndView("redirect:/testget");
// 入力値のチェック
if (intvalue <= 0) {
redirectAttributes.addFlashAttribute("error", "intvalueは0より大きい値でなければいけません");
return modelAndView;
}
// データをセット
modelAndView.addObject("value", intvalue);
return modelAndView;
}
@Test
void testpost() throws Exception {
// パラメータを渡さなかったらチェックに引っかかる
this.mockMvc.perform(post("/testpost")).andExpect(redirectedUrl("/testget"))
.andExpect(flash().attribute("error", "intvalueは0より大きい値でなければいけません"));
// パラメータを渡したらチェックを通る
this.mockMvc.perform(post("/testpost").param("intvalue", "5"))
.andExpect(redirectedUrl("/testget?value=5"));
}
redirectedUrl()を使ってリダイレクト先のURLをテストします。
flash()を使ってredirectAttributes.addFlashAttribute()のテストをします。
POSTで受け取るパラメータはparam()で指定できます。
特定のメソッドが特定の引数でコールされたかどうかをテスト
ControllerでPOSTを受け取ったときに
何かしらの処理を行うと思います。そのテストです。
ここでは、特定のServiceをControllerにDIしておいて、
そのServcieのメソッドをPost処理の中でコールすることのテストとします。
@Service
public class MyService {
public void test(int value) {
System.out.println("MyService.test()..." + value);
}
}
@Controller
@EnableAutoConfiguration
public class HomeController {
@Autowired
MyService myService;
@PostMapping(path = "/testpost2")
public String testpost2(@RequestParam(value = "intvalue", required = false, defaultValue = "0") Integer intvalue) {
// MyServiceのtest()をコール
myService.test(intvalue);
return "redirect:/";
}
}
@SpringBootTest(classes = HomeController.class)
@AutoConfigureMockMvc
public class HomeControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private MyService myService;
@Test
void testpos2() throws Exception {
// redirectUrlチェック
this.mockMvc.perform(post("/testpost2").param("intvalue", "5")).andExpect(redirectedUrl("/"));
// MyServiceのtest()メソッドが引数の値5でコールされたかどうかをテスト
verify(this.myService, times(1)).test(5);
}
}
verify(this.myService, times(1)).test(5);
と書くと、
「MyServiceクラスのtestメソッドを引数の値5で1回コールされた」
というテストになります。
2回コールされたかどうかのテストは
verify(this.myService, times(2)).test(5);
となりますし、
引数の値が10であって欲しい時は
verify(this.myService, times(1)).test(10);
と書きます。
これによって、
コントローラーのテストは
パラメータの値をチェックして処理を切り分けることと、
他サービスの関数にパラメータの値を渡すこと
の2種類に絞られるかと思います。
Getのパラメータのテスト
Postのパラメータはparams()で指定しましたが、
GetのパラメータはURLで指定します。
@GetMapping(path = "/testget")
String testget(@RequestParam(value = "value", required = false, defaultValue = "0") Integer value, @ModelAttribute("error") String error, Model model) {
model.addAttribute("strError", error);
model.addAttribute("intValue", value);
// testget.htmlを指定する
return "testget";
}
@Test
void testget() throws Exception {
this.mockMvc.perform(get("/testget?value=5&error=SuperError")).andDo(print())
// ステータス「200 OK」を返すかどうか
.andExpect(status().isOk())
// テンプレート「testget」を返すかどうか
.andExpect(view().name("testget"))
// Modelのテスト。
.andExpect(model().attribute("intValue", 5))
.andExpect(model().attribute("strError", "SuperError"))
;
}
おわりに
まだまだ、spring bootでのテストを書き始めた時点でのメモですので、
今後も加筆していきたいと思います。
サンプルコードはGitHubにあげてます。
おわり。