5
10

More than 3 years have passed since last update.

Spring Boot2のユニットテストの書き方メモ

Posted at

はじめに

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にデータを詰めますよね。

MyData.java
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;
  }
}
HomeController.java
@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";
  }
}
HomeControllerTest.java
@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しますよね。

HomeController.java
  @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;
  }
HomeControllerTest.java
  @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処理の中でコールすることのテストとします。

MyService.java
@Service
public class MyService {
  public void test(int value) {
    System.out.println("MyService.test()..." + value);
  }
}
HomeController.java

@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:/";
  }
}
HomeControllerTest.java
@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で指定します。

HomeController.java
  @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";
  }
HomeControllerTest.java
  @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にあげてます。

おわり。

5
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
10