통합 테스트가 아닌 controller layer만 분리하여 테스트
- controller layer에서 처리되는 값을 검증하기 위한 테스트- ex ) http status, model, view, response body 등등….
 
테스트 컨트롤러
- 많이 사용되는 요청과 응답을 케이스별로 정리- 요청 파라미터가 없을 때
- @RequestParam으로 값을 받을 때
- @PathVariable로 값을 받을 때
- @ModelAttribute로 값을 받을 때
- @RequestBody로 값을 받을 때
- 파일 데이터를 값으로 받을 때
- 리턴하는 값이 JSON형태일 때 어떻게 값을 검증하는지.
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
@Getter
@Setter
public class TestReq {
  private String id;
  private String password;
  public TestReq() {
  }
  public TestReq(String id, String password) {
    this.id = id;
    this.password = password;
  }
}
@Controller
@RequestMapping(path = "/controller")
public class IntegratedController {
  private final RandomUtil randomUtil;
  @Autowired
  public IntegratedController(RandomUtil randomUtil) {
    this.randomUtil = randomUtil;
  }
  @GetMapping(path = "/get-no-parameter")
  public String getController1(Model model) {
    model.addAttribute("message", "hello mockito!");
    randomModelAttributeSet(model);
    return "/controller/get";
  }
  @GetMapping(path = "/get-request-param")
  public String getController2(@RequestParam("param") String param, Model model) {
    model.addAttribute("message", "hello mockito!");
    randomModelAttributeSet(model);
    model.addAttribute("param", param);
    return "/controller/get";
  }
  @GetMapping(path = "/get-request-param/{id}")
  public String getController3(@PathVariable("id") String id, Model model) {
    model.addAttribute("message", "hello mockito!");
    randomModelAttributeSet(model);
    model.addAttribute("pathVariable", id);
    return "/controller/get";
  }
  @PostMapping(path = "/post-x-www-form-urlencoded")
  public String postController1(@ModelAttribute("testReq") TestReq testReq, Model model) {
    model.addAttribute("message", "hello mockito!");
    randomModelAttributeSet(model);
    model.addAttribute("testReq", testReq);
    return "/controller/post";
  }
  @PostMapping(path = "/post-request-body")
  public String postController2(@RequestBody TestReq testReq, Model model) {
    model.addAttribute("message", "hello mockito!");
    randomModelAttributeSet(model);
    model.addAttribute("testReq", testReq);
    return "/controller/post";
  }
  @PostMapping(path = "/post-multipart-file/{id}")
  public String postController2(
    @PathVariable("id") String id,
    @RequestParam("file") MultipartFile multipartFile,
    Model model
  ) {
    model.addAttribute("message", "hello mockito!");
    randomModelAttributeSet(model);
    model.addAttribute("multipartFile", multipartFile);
    model.addAttribute("id", id);
    return "/controller/post";
  }
  @ResponseBody
  @PostMapping(path = "/rest-api-response_check")
  public TestReq postController3(
  ) {
    return new TestReq("홍길동", "12345");
  }
  private void randomModelAttributeSet(Model model) {
    model.addAttribute("uuid", randomUtil.randomUUID());
    model.addAttribute("integer", randomUtil.randomIntegerNumber());
  }
}
테스트 기본 설정 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@ExtendWith(MockitoExtension.class)
@WebMvcTest(
  controllers = {
    IntegratedController.class
  }
)
@AutoConfigureMockMvc
public class ControllerTestFoundation {
  @Autowired
  private MockMvc mockMvc;
  @MockBean
  private RandomUtil randomUtil;
  private static final ObjectMapper objectMapper = new ObjectMapper();
  @BeforeEach
  void beforeEach() {
    defaultModelValueSetup();
  }
  private void defaultModelValueSetup() {
    when(randomUtil.randomUUID()).thenReturn("random-uuid");
    when(randomUtil.randomIntegerNumber()).thenReturn(10);
  }
}
ExtendWith
- @ExtendWith(MockitoExtension.class)- controller layer가 의존하고 있는- bean을 모킹해주는 어노테이션을 사용 가능하게 해줌.
 
WebMvcTest
- @WebMvcTest: 애플리케이션의- controller layer만을 테스트할 때 사용- controllers에 테스트한 컨트롤러를 설정할 수 있음. 설정하지 않을시 모든 컨트롤러를 대상으로함
- excludeAutoConfiguration, excludeFilters해당 설정을 하면 제외하고싶은 필터, 기본 설정을 무시할 수 있다.- excludeAutoConfiguration = SecurityAutoConfiguration.class이런 설정을 추가하면- security를 무시할 수 있음.
 
- @SpringBootTest의 차이점 :- @SpringBootTest는 전체 레이어를 대상으로 테스르할 때 사용
 
private MockMvc mockMvc
- 테스트 코드에서 컨트롤러에 요청을 보내고 리턴받는 값들을 검증할 수 있게 해주는 클래스
MockBean
- @MockBean- 컨트롤러 레이어에서 의존하고있는 bean을 테스트 가능할 수 있도록 가짜bean을 만들어 주입해주는 어노테이션- 해당 테스트 에서는 private final RandomUtil randomUtil<= 이거에 해당
 
- 해당 테스트 에서는 
 
- 컨트롤러 레이어에서 의존하고있는 
@BeforeEach
- @BeforeEach- 각각의 @Test를 실행전에 실행되는 메소드, 해당 설정을 함으로써 테스트에 필요한 값이나 설정을 테스트마다 동일하게 할 수 있다.
 
- 각각의 
defaultModelValueSetup()
- 테스트 컨트롤러가 의존하고 있는 계층의 값들에 가짜 값을 주입하는 코드- private RandomUtil randomUtil해당 서비스에서 제공해주는 값들을- stubbing해주는 코드
 
controller_get_no_parameter
- 요청 파라미터가 없는 컨트롤러 테스트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Test
@DisplayName("view 리턴 컨트롤러 - get no parameter")
void controller_get_no_parameter() throws Exception {
  MvcResult mvcResult = mockMvc.perform(
      get("/controller/get-no-parameter")
    )
    .andExpect(status().isOk())
    .andReturn();
  ModelAndView modelAndView = mvcResult.getModelAndView();
  Assertions.assertNotNull(modelAndView);
  Map<String, Object> model = modelAndView.getModel();
  Assertions.assertNotNull(model.get("message"));
  Assertions.assertNotNull(model.get("uuid"));
  Assertions.assertNotNull(model.get("integer"));
  String viewName = modelAndView.getViewName();
  Assertions.assertEquals("/controller/get", viewName);
}
controller_get_request_param()
- 요청 파라미터를 url query string으로 받는 컨트롤러 테스트- 요청 url 뒤에 url/param={param}형식으로 값을 바인딩하고 다음 인자에 바인딩될 값을 설정
 
- 요청 url 뒤에 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Test
@DisplayName("view 리턴 컨트롤러 - get - request param")
void controller_get_request_param() throws Exception {
  MvcResult mvcResult = mockMvc.perform(
      get("/controller/get-request-param?param={param}", "param")
    )
    .andExpect(status().isOk())
    .andReturn();
  ModelAndView modelAndView = mvcResult.getModelAndView();
  Assertions.assertNotNull(modelAndView);
  Map<String, Object> model = modelAndView.getModel();
  Assertions.assertNotNull(model.get("message"));
  Assertions.assertNotNull(model.get("uuid"));
  Assertions.assertNotNull(model.get("integer"));
  Assertions.assertNotNull(model.get("param"));
  String viewName = modelAndView.getViewName();
  Assertions.assertEquals("/controller/get", viewName);
}
controller_get_path_variable()
- @PathVariable로 값을 받는 컨트롤러 테스트- 테스트 형식이 url query string방식과 거의 동일하다
 
- 테스트 형식이 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Test
@DisplayName("view 리턴 컨트롤러 - get - path variable")
void controller_get_path_variable() throws Exception {
  MvcResult mvcResult = mockMvc.perform(
      get("/controller/get-request-param/{id}", "pathVariable")
    )
    .andExpect(status().isOk())
    .andReturn();
  ModelAndView modelAndView = mvcResult.getModelAndView();
  Assertions.assertNotNull(modelAndView);
  Map<String, Object> model = modelAndView.getModel();
  Assertions.assertNotNull(model.get("message"));
  Assertions.assertNotNull(model.get("uuid"));
  Assertions.assertNotNull(model.get("integer"));
  Assertions.assertEquals("pathVariable", model.get("pathVariable"));
  String viewName = modelAndView.getViewName();
  Assertions.assertEquals("/controller/get", viewName);
}
controller_post_x_www_form_urlencoded()
- 요청 값을 x-www-form-urlencoded형태로 받는 컨트롤러 테스트- perform()메소드 안에서 요청 url을 설정한 다음에- .param()을 이용해서 값 셋팅.
- .params()를 이용해서- MultiValueMap<String, String> params형식으로 보낼 수도 있음.
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Test
@DisplayName("view 리턴 컨트롤러 - post - x-www-form-urlencoded")
void controller_post_x_www_form_urlencoded() throws Exception {
  MvcResult mvcResult = mockMvc.perform(
      post("/controller/post-x-www-form-urlencoded")
        .param("id", "홍길동")
        .param("password", "12345")
    )
    .andExpect(status().isOk())
    .andReturn();
  ModelAndView modelAndView = mvcResult.getModelAndView();
  Assertions.assertNotNull(modelAndView);
  Map<String, Object> model = modelAndView.getModel();
  Assertions.assertNotNull(model.get("message"));
  Assertions.assertNotNull(model.get("uuid"));
  Assertions.assertNotNull(model.get("integer"));
  Assertions.assertNotNull(model.get("testReq"));
  Assertions.assertInstanceOf(TestReq.class, model.get("testReq"));   //  타입 체크
  TestReq testReq = (TestReq) model.get("testReq");
  Assertions.assertEquals("홍길동", testReq.getId());
  Assertions.assertEquals("12345", testReq.getPassword());
  String viewName = modelAndView.getViewName();
  Assertions.assertEquals("/controller/post", viewName);
}
controller_post_request_body()
- 요청 파라미터가 Json일 때 컨트롤러 테스트- ObjectMapper를 이용하여 전달할 값을- JSON형태로 만든 후 테스트 이후 전달받은 값이 잘 바인딩 되었는지까지 검증
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Test
@DisplayName("view 리턴 컨트롤러 - post - request body")
void controller_post_request_body() throws Exception {
  TestReq req = new TestReq("홍길동", "12345");
  MvcResult mvcResult = mockMvc.perform(
      post("/controller/post-request-body")
        .contentType(MediaType.APPLICATION_JSON_VALUE)
        .content(objectMapper.writeValueAsString(req))
    )
    .andExpect(status().isOk())
    .andReturn();
  ModelAndView modelAndView = mvcResult.getModelAndView();
  Assertions.assertNotNull(modelAndView);
  Map<String, Object> model = modelAndView.getModel();
  Assertions.assertNotNull(model.get("message"));
  Assertions.assertNotNull(model.get("uuid"));
  Assertions.assertNotNull(model.get("integer"));
  Assertions.assertNotNull(model.get("testReq"));
  Assertions.assertInstanceOf(TestReq.class, model.get("testReq"));   //  타입 체크
  TestReq testReq = (TestReq) model.get("testReq");
  Assertions.assertEquals("홍길동", testReq.getId());
  Assertions.assertEquals("12345", testReq.getPassword());
  String viewName = modelAndView.getViewName();
  Assertions.assertEquals("/controller/post", viewName);
}
controller_post_multipart_file()
- MultipartFile를 요청값으로 받는 컨트롤러 테스트- MultipartFile을 모킹해주는 객체- MockMultipartFile선언 후 테스트
- 파일 데이터와 다른 데이터를 같이 보낼 수 있다.
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Test
@DisplayName("view 리턴 컨트롤러 - post - multipart file form data")
void controller_post_multipart_file() throws Exception {
  MockMultipartFile file = new MockMultipartFile(
    "file", // 파일 파라미터 이름
    "test.txt", // 파일 이름
    "text/plain", // 파일 타입
    "Hello, World!".getBytes() // 파일 내용
  );
  MvcResult mvcResult = mockMvc.perform(
      multipart("/controller/post-multipart-file/{id}", "id")
        .file(file)
    )
    .andExpect(status().isOk())
    .andReturn();
  ModelAndView modelAndView = mvcResult.getModelAndView();
  Assertions.assertNotNull(modelAndView);
  Map<String, Object> model = modelAndView.getModel();
  Assertions.assertNotNull(model.get("message"));
  Assertions.assertNotNull(model.get("uuid"));
  Assertions.assertNotNull(model.get("integer"));
  Assertions.assertNotNull(model.get("multipartFile"));
  Assertions.assertNotNull(model.get("id"));
  String viewName = modelAndView.getViewName();
  Assertions.assertEquals("/controller/post", viewName);
}
rest_controller_response_body_value()
- Json형태로 반환되는- response값을 검증하는 테스트- jsonPath를 이용하여 검증 가능
 
1
2
3
4
5
6
7
8
9
10
    @Test
    @DisplayName("rest api 컨트롤러 - response body 값 검증")
    void rest_controller_response_body_value() throws Exception {
        mockMvc.perform(
                        post("/controller/rest-api-response_check")
                )
                .andExpect(jsonPath("$.id").value("홍길동"))
                .andExpect(jsonPath("$.password").value("12345"));
    }