概要
SpringBootの入力欄にバリデーションを追加していきます。
テストコードも書きます。
テストコードを書く上で工夫したこと
プロダクトコードにおいてバリデーションの責務はモデルにまとめさせる。
コントローラーでの処理はモデルのメソッド呼んで処理する。
コントローラーにはなるべく書かないようにする
動作環境
OS: Windows10
IDE: IntelliJCommunity
SpringBoot version 2.7.0
Java:17
動かし方
1.以下URLからリポジトリをクローン
https://github.com/RYA234/spring_boot_memo
branchは「validation」を選択
2.src/main/java/com/example/spring_boot_memo/SpringBootMemoApplicationを実行します。
3.以下URLを開きます
http://localhost:5000/
開くと以下画面が表示されます。
内容
画面
不適切な値を入れるとメッセージ出します。至ってシンプルな構造ですね
プロダクトコード
model
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
<!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
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を使っています。
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()
);
}
}
感想
アノテーション付ければModelにValidationの責務つけるのは楽でした。
処理がview-Controller間であるためそこの関係を理解するのに苦労しました。
(BindingResultの動きがわからない)
実践で使う場合、コントローラーに他の処理が混ざるので実装難易度が高くなると予想。
うまくモックできたら良いですけどね…
コントローラーのテストは苦手ですので今後の課題ですね