SpringのControllerクラスのテストコード

## はじめに
SpringのControllerクラスのテストをJUnitで行う。

## テスト対象クラス

登録された商品の一覧の取得機能（displayList()）、商品IDで検索し商品を取得する機能（displayDetails）を実装した。
Service層はモック化するため紹介を省くが、リポジトリ層を呼び出しDBにアクセスする。
DBから取得した値をmodelに格納してからviewに返却しthymeleafを使って表示する。

```java:CatalogController.java
@Controller
@RequestMapping("/catalog")
public class CatalogController {
    private final CatalogService catalogService;

    public CatalogController(CatalogService catalogService) {
        this.catalogService = catalogService;
    }

    @GetMapping("/display-list")
    public String displayList(Model model) {
        List<Product> products = catalogService.findAll();
        model.addAttribute("productList", products);
        return "catalog/productList";
    }

    @GetMapping("/display-details")
    public String displayDetails(@RequestParam String productId, Model model) {
        Product product = catalogService.findById(productId);
        model.addAttribute("product", product);
        return "catalog/productDetails";
    }
}
```

## Controlleクラスのテスト
つづいてCatalogController.javaをテストする。
単にメソッドをテストするだけであれば、テストクラスから直接メソッドを呼び出せばテストを行えるが、これでは@GetMapping()などのアノテーションが機能しているかのテストができない。
アノテーションも含めてテストするために、MockMvcを利用する。


```java:CatalogControllerTest.class
@WebMvcTest(CatalogController.class)
class CatalogControllerTest {

	@Autowired
    MockMvc mockMvc;

    @MockBean
    CatalogService catalogService;
    
	@Test
	void displayList_test() throws Exception {
		List<Product> products = new ArrayList<Product>();
		Product product = new Product();
        product.setName("商品01");
        products.add(product);
        product = new Product();
        product.setName("商品02");
        products.add(product);
        
		doReturn(products).when(catalogService).findAll();
		
		mockMvc.perform(
                get("/catalog/display-list")
        )
		.andDo(print())
        .andExpect(content().string(containsString("商品01")))
        .andExpect(content().string(containsString("商品02")))
        .andExpect(view().name("catalog/productList"));
	}
	
	@Test
	void displayDetails_test() throws Exception {
		Product product = new Product();
        product.setName("商品01");
        
        doReturn(product).when(catalogService).findById("p01");
        
        mockMvc.perform(
                get("/catalog/display-details")
                .param("productId", "p01")
        )
		.andDo(print())
        .andExpect(content().string(containsString("商品01")))
        .andExpect(view().name("catalog/productDetails"));
	}
	
}
```

@WebMvcTestでテスト対象クラスを指定する。
MockMvcをインジェクションし、@MockBeanにテスト対象クラスのインスタンスをモック化する。

doReturn()にモック化するオブジェクトの戻り値を指定する。
when()にモック化したオブジェクトを指定し、それに続いてメソッドを呼び出す。

```
doReturn(products).when(catalogService).findAll();
```

テスト対象のメソッドの指定と、モック化したオブジェクト（catalogService）のメソッドの引数を指定する。
get()にはControllerクラスに記載したエントリポイントを指定する。
param()にはモック化したオブジェクトのメソッドの引数を指定する。
```
mockMvc.perform(
                get("/catalog/display-details")
                .param("productId", "p01")
        )
```

続いてテストの期待値の指定を行う。
content()には戻り値（ここではviewに返すHTML）を指定する。戻り値に「商品01」が含まれているかを確認する。
view()には返却するview名を確認する。
```
        .andDo(print())
        .andExpect(content().string(containsString("商品01")))
        .andExpect(content().string(containsString("商品02")))
        .andExpect(view().name("catalog/productList"));
```
.andDo(print())でコンソールに戻り値を表示し、視覚的に確認することができる。
テストケースが膨大な場合は、コンソール出力で処理が重くなるため注意。
コンソール出力を一部抜粋する。
```
MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /catalog/display-list
       Parameters = {}
          Headers = []
             Body = null
    Session Attrs = {}

Handler:
             Type = com.example.shopping.controller.CatalogController
           Method = com.example.shopping.controller.CatalogController#displayList(Model)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = catalog/productList
             View = null
        Attribute = productList
            value = [com.example.shopping.entity.Product@34549979, com.example.shopping.entity.Product@144a5e6e]

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Language:"en", Content-Type:"text/html;charset=UTF-8"]
     Content type = text/html;charset=UTF-8
             Body = <!DOCTYPE HTML>
<html>
    <head>
        <title>商品一覧</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <h1>商品一覧</h1>
        <table>
          <tr>
            <th>商品名</th>
            <th>価格</th>
            <th>在庫数</th>
          </tr>
          <tr>
            <td><a href="/catalog/display-details?productId="><span>商品01</span></a></td>
            <td><span></span>円</td>
            <td><span></span></td>
          </tr>
          <tr>
            <td><a href="/catalog/display-details?productId="><span>商品02</span></a></td>
            <td><span></span>円</td>
            <td><span></span></td>
          </tr>
        </table>
    </body>
</html>
    Forwarded URL = null
   Redirected URL = null
          Cookies = []
```

## おわりに
@RestControllerを指定した、所謂REST APIのテストコードも書いて学習したい。

## 参考
[Controllerクラスのテスト](https://qiita.com/ryo2132/items/ec10116238e1e1f333a1)
[プロになるためのSpring入門ーーゼロからの開発力養成講座](https://www.amazon.co.jp/%E3%83%97%E3%83%AD%E3%81%AB%E3%81%AA%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AESpring%E5%85%A5%E9%96%80%E3%83%BC%E3%83%BC%E3%82%BC%E3%83%AD%E3%81%8B%E3%82%89%E3%81%AE%E9%96%8B%E7%99%BA%E5%8A%9B%E9%A4%8A%E6%88%90%E8%AC%9B%E5%BA%A7-%E5%9C%9F%E5%B2%90-%E5%AD%9D%E5%B9%B3/dp/4297136139/ref=sr_1_1?adgrpid=150532863105&dib=eyJ2IjoiMSJ9.LeZ92ouObJOtnTu0BuQ0KhLGS_fAioqopPm1l1ly0RUFfRqu_ITZRowIO-xwrH_-quEAYo2GT4QoywkpGX4rvBY2WUXN5WtZKeZT4FTJFqiO1IacVecEjYtsB-gM5spirk10Ou4aNQg_h66jCzMHpq6zKR9j8wUorEjidrRvc57Kuuy8tHyQwGi78kH_dwIfbqA9hsKOtUxe0TS-i8-C6PKFrE8qH1bIv3KNgG4oFDgIMXoLFtFLrFCfH2H3c6nCoQC_eDh0pAokB27Zsa3PqXEzS_pMia6ctzfk5OlUOY4.S4V-ovnRRJo2BdGlYPiISgfapeJsd0b0qXC-s7TtMDk&dib_tag=se&hvadid=665373750209&hvdev=c&hvlocphy=9159433&hvnetw=g&hvqmt=e&hvrand=16494488955806990697&hvtargid=kwd-2155575668788&hydadcr=14092_13515343&jp-ad-ap=0&keywords=%E3%83%97%E3%83%AD%E3%81%AB%E3%81%AA%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AEspring%E5%85%A5%E9%96%80&qid=1711440517&sr=8-1)