SpringBoot Validation（単体テストあり）

# 概要
SpringBootの入力欄にバリデーションを追加していきます。
テストコードも書きます。

テストコードを書く上で工夫したこと

プロダクトコードにおいてバリデーションの責務はモデルにまとめさせる。
コントローラーでの処理はモデルのメソッド呼んで処理する。
**コントローラーにはなるべく書かないようにする**

# 動作環境
OS:　Windows10
IDE:　IntelliJCommunity
SpringBoot version 2.7.0
Java:17

# 動かし方
１．以下URLからリポジトリをクローン
https://github.com/RYA234/spring_boot_memo
branchは「validation」を選択

2.src/main/java/com/example/spring_boot_memo/**SpringBootMemoApplication**を実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/b75613d3-0eb0-5dc2-758f-edb3fe0df4f0.png)


3.以下URLを開きます
http://localhost:5000/
開くと以下画面が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/953ed9a8-764e-8734-6cdb-4dbc31361a88.png)

# 内容
## 画面
不適切な値を入れるとメッセージ出します。至ってシンプルな構造ですね
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/d663e9a1-9d00-b64f-5103-95dbcc12c83f.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/e10368a0-ca7f-fb01-071d-15486b821167.png)



## プロダクトコード
model
``` ModelStaff.java
package com.example.spring_boot_memo.validation;

import lombok.Data;
import org.jetbrains.annotations.MustBeInvokedByOverriders;
import org.springframework.validation.BindingResult;

import javax.validation.constraints.*;

@Data
public class ModelStaff {
    @Min(value = 0)
    @Max(value = 100)
    public Integer id;

      @NotEmpty(message="スタッフ名を入力してください")
      @Size(max = 15, message = "スタッフの名前は15字以内で入力してください")
      String name;

 BindingResult bindingResult;
}

```

view
``` main.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"></meta>
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>Validation</title>

</head>
<body>
thymeleaf

    <form method="post" action="/validation/check"  th:object="${modelStaff}"  >
        <input type ="number" th:field="*{id}"  th:errorclass="is-invalid">
        <p class="invalid-feedback" th:errors="*{id}">Id Error</p>
        </br>  </br>
        <input type ="text" th:field="*{name}"  th:errorclass="is-invalid">
        <p class="invalid-feedback" th:errors="*{name}">Name Error</p>
        </br>  </br>
        <button type="submit" name="OK">
            バリデーションショック
        </button>

    </form>

</body>
</html>
```

Controller
``` MainController.java
package com.example.spring_boot_memo.validation;


import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import java.io.IOException;

@org.springframework.stereotype.Controller
public class MainController {

    @RequestMapping(value = "index", method = RequestMethod.GET)
    public String indexController(Model model,ModelStaff modelStaff)
    {
        System.out.println("aaa");

        return "index";
    }

    @RequestMapping(value = "validation/main", method = RequestMethod.GET)
    public String validationController(Model model)
    {
        System.out.println("aaa");
        ModelStaff modelStaff = new ModelStaff();
        model.addAttribute("modelStaff",modelStaff);

        return "validation/main";
    }


    @RequestMapping(value = "validation/check",params ="OK", method = RequestMethod.POST)
    public String validationCheckController(Model model, @Validated ModelStaff modelStaff, BindingResult bindingResult) throws IOException
    {


        System.out.println(modelStaff);
        if(bindingResult.hasErrors())
        {
            model.addAttribute("modelStaff",modelStaff);
            return "validation/main";
        }

        return "validation/check";
    }

}

```

## テストコード
テストコード書くために、Mockito と MockMvcを使っています。
``` ValidationTest.java
package com.example.spring_boot_memo;


import com.example.spring_boot_memo.validation.MainController;
import com.example.spring_boot_memo.validation.ValiService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;

@AutoConfigureMockMvc
@SpringBootTest
public class ValidationTest {

    @Autowired
    MockMvc mockMvc;


    @Mock
    ValiService valiService;
    @InjectMocks
    MainController mainController;

    @BeforeEach
    void setup()
    {
        MockitoAnnotations.initMocks(this);
        this.mockMvc = MockMvcBuilders.standaloneSetup(mainController).build();
    }

    @Test
    @DisplayName("1.name=ASBA id=11　結果：OK")
     public void Case1() throws Exception{
       this.mockMvc.perform(post("/validation/check")
                .param("OK","OK")
                       .param("name","ASBA")
                       .param("id","11")
               )
               .andDo(print())
               .andExpect(model().hasNoErrors()
                       );
    }

    @Test
    @DisplayName("2.name=ASBA id=101 結果：NG")
    public void Case2() throws Exception{
        this.mockMvc.perform(post("/validation/check")
                        .param("OK","OK")
                        //   .param("name","dsada"))
                        .param("name","ASBA")
                        .param("id","101")
                )
                .andDo(print())
                .andExpect(model().hasErrors()
                );
    }

    @Test
    @DisplayName("3.name=ASBA id=100 結果：OK")
    public void Case3() throws Exception{
        this.mockMvc.perform(post("/validation/check")
                        .param("OK","OK")
                        .param("name","ASBA")
                        .param("id","100")
                )
                .andDo(print())
                .andExpect(model().hasNoErrors()
                );
    }

    @Test
    @DisplayName("4.name=ASBA id=100 結果：OK")
    public void Case4() throws Exception{
        this.mockMvc.perform(post("/validation/check")
                        .param("OK","OK")
                        .param("name","ASBA")
                        .param("id","100")
                )
                .andDo(print())
                .andExpect(model().hasNoErrors()
                );
    }

    @Test
    @DisplayName("5.name=ASBA id=0 結果：OK")
    public void Case5() throws Exception{
        this.mockMvc.perform(post("/validation/check")
                        .param("OK","OK")
                        .param("name","ASBA")
                        .param("id","0")
                )
                .andDo(print())
                .andExpect(model().hasNoErrors()
                );
    }

    @Test
    @DisplayName("6.name=null id=50 結果：NG")
    public void Case6() throws Exception{
        this.mockMvc.perform(post("/validation/check")
                        .param("OK","OK")
                        .param("name","")
                        .param("id","50")
                )
                .andDo(print())
                .andExpect(model().hasErrors()
                );
    }

    @Test
    @DisplayName("7.name=ASBA id=-1 結果：NG")
    public void Case7() throws Exception{
        this.mockMvc.perform(post("/validation/check")
                        .param("OK","OK")
                        .param("name","ASBA")
                        .param("id","-1")
                )
                .andDo(print())
                .andExpect(model().hasErrors()
                );
    }

}


```
実行結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/86a2524a-970b-a029-4575-d2102e4b6bdc.png)

# 感想
アノテーション付ければModelにValidationの責務つけるのは楽でした。

処理がview-Controller間であるためそこの関係を理解するのに苦労しました。
（BindingResultの動きがわからない）

実践で使う場合、コントローラーに他の処理が混ざるので実装難易度が高くなると予想。
うまくモックできたら良いですけどね…
コントローラーのテストは苦手ですので今後の課題ですね

# 参考資料


https://qiita.com/ryo2132/items/ec10116238e1e1f333a1#validation-error%E3%81%8C%E3%81%82%E3%82%8B%E5%A0%B4%E5%90%88