![[Spring test] mockito, controller에서 null을 반환한다 [Spring test] mockito, controller에서 null을 반환한다](http://t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png)
컨트롤러 테스트 작성 중 이슈가 생겼다. Mock 객체를 생성하기 위해 Mockito 라이브러리를 활용하여 컨트롤러의 반환값을 지정해 주었다. 하지만 실제 테스트 결과에서는 반환값으로 지정한 객체가 들어있지 않고 null이 포함되어 있다. 이게 대체 무슨 일…?
![[Spring test] mockito, controller에서 null을 반환한다 [Spring test] mockito, controller에서 null을 반환한다](http://t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png)
![[Spring test] mockito, controller에서 null을 반환한다 [Spring test] mockito, controller에서 null을 반환한다](http://t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png)
Expected to find an object with property ['accessToken'] in path $['data'] but found 'null'. This is not a json object according to the JsonProvider: 'com.jayway.jsonpath.spi.json.JsonSmartJsonProvider'.
com.jayway.jsonpath.PathNotFoundException: Expected to find an object with property ['accessToken'] in path $['data'] but found 'null'. This is not a json object according to the JsonProvider: 'com.jayway.jsonpath.spi.json.JsonSmartJsonProvider'.
![[Spring test] mockito, controller에서 null을 반환한다 [Spring test] mockito, controller에서 null을 반환한다](http://t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png)
해결방법
바쁜 현대인을 위해서 해결법을 먼저 제시한다. Mockito에서 mock 객체에 원하는 반환값을 반환하기 위해서는 단순히 지정한 값과 동일한 입력값을 넣어줘야 한다는 개념보다 섬세하다.
Mockito 내부에서 값을 비교할 때, ==(주소 비교)를 수행하기 때문에 단순히 값만 같다고 해서는 무조건 동일한 객체로 인식되지 않는다. 따라서 아래 방법을 소개한다.
1. @Data(lombok 사용 가정)
비교에 필요한 객체에 @Data 애너테이션을 붙여 강제로 equals()와 hashcode() 코드를 주입할 수 있다. 따라서 Mockito가 ==로 객체를 비교를 할 때 값이 같다면 동일한 객체로 볼 수 있게 구현한다.
물론 @Data 애너테이션을 사용하지 않는 상황에서도 equals()나 hashcode()를 직접 구현하여 사용해도 무관하다.
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
public class SignInReqDto {
String id;
String pw;
}
@Test
void success_to_signIn() throws Exception {
// given
SignInReqDto reqDto = new SignInReqDto("aaa", "bbb");
SignInResDto resDto = new SignInResDto("aaa", "mockAccessToken", new Date());
// Wrap resDto in BaseResponse
given(authService.signIn(reqDto)).willReturn(resDto);
// when & then
mockMvc.perform(
post("/v1/auth/signin")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(reqDto))
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.accessToken").value("mockAccessToken"));
}
![[Spring test] mockito, controller에서 null을 반환한다 - undefined - 1. @Data(lombok 사용 가정) [Spring test] mockito, controller에서 null을 반환한다 - undefined - 1. @Data(lombok 사용 가정)](https://blog.kakaocdn.net/dn/caeF79/btsMQ39Uvlj/M0rNQO2XeuLXqQlm8yh5Ak/img.png)
2. any()
org.mockito.ArgumentMatchers.any;를 사용하는 것도 방법 중 하나이다. 요청하는 객체의 모든 인스턴스를 허용하는 방식으로 활용은 편리하지만 테스트에 주의가 필요하다. 특정 필드를 검사하고 실패 시의 반환값을 반환해야 하는 케이스에서는 활용하기 어렵다.
@Test
void success_to_signIn() throws Exception {
// given
SignInReqDto reqDto = new SignInReqDto("aaa", "bbb");
SignInResDto resDto = new SignInResDto("aaa", "mockAccessToken", new Date());
// Wrap resDto in BaseResponse
given(authService.signIn(any(SignInReqDto.class))).willReturn(resDto);
// when & then
mockMvc.perform(
post("/v1/auth/signin")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(reqDto))
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.accessToken").value("mockAccessToken"));
}
![[Spring test] mockito, controller에서 null을 반환한다 - undefined - 2. any() [Spring test] mockito, controller에서 null을 반환한다 - undefined - 2. any()](https://blog.kakaocdn.net/dn/ce9uI8/btsMQcfv72q/pkiIhsbMbJeuyVTKINS1l1/img.png)
3. ArgumentMatchers.argThat()
객체 전부를 비교하는 것이 아니라 객체의 특정 필드 값만 비교하는 방식이다. any()가 테스트하게 되는 범위가 너무 넓어서 불만이라면 사용할 수 있다. 커스텀 검증에 큰 장점이 있지만, 코드가 길어지고 가독성이 떨어진다.
@Test
void success_to_signIn() throws Exception {
// given
SignInReqDto reqDto = new SignInReqDto("aaa", "bbb");
SignInResDto resDto = new SignInResDto("aaa", "mockAccessToken", new Date());
// Wrap resDto in BaseResponse
given(authService.signIn(any(SignInReqDto.class))).willReturn(resDto);
given(authService.signIn(argThat(dto ->
"aaa".equals(dto.getId()) && "bbb".equals(dto.getPw()))
)).willReturn(resDto);
// when & then
mockMvc.perform(
post("/v1/auth/signin")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(reqDto))
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.accessToken").value("mockAccessToken"));
}
![[Spring test] mockito, controller에서 null을 반환한다 - undefined - 3. ArgumentMatchers.argThat() [Spring test] mockito, controller에서 null을 반환한다 - undefined - 3. ArgumentMatchers.argThat()](https://blog.kakaocdn.net/dn/bkagHw/btsMRQWidBf/6AgSfaBBNQTNxwLrucK6G1/img.png)
근데 왜 이러는거야?
![[Spring test] mockito, controller에서 null을 반환한다 - 근데 왜 이러는거야? [Spring test] mockito, controller에서 null을 반환한다 - 근데 왜 이러는거야?](https://blog.kakaocdn.net/dn/bxfv2z/btsMRjLsmd7/WrXtKI4KCDyUBaYGCuaXVk/img.png)
천천히 디버그 모드로 확인해보자. 테스트 코드에서 직접 생성한 SignInReqDto의 주소는 8870번이다.
![[Spring test] mockito, controller에서 null을 반환한다 - 근데 왜 이러는거야? [Spring test] mockito, controller에서 null을 반환한다 - 근데 왜 이러는거야?](https://blog.kakaocdn.net/dn/Q4Cpp/btsMQBFZLUz/VB345ppNTpkNCGc4UfH9Hk/img.png)
반면 Controller에 전달된 signInReqDto의 주소는 9200번이다.
![[Spring test] mockito, controller에서 null을 반환한다 - 근데 왜 이러는거야? [Spring test] mockito, controller에서 null을 반환한다 - 근데 왜 이러는거야?](https://blog.kakaocdn.net/dn/1h3lT/btsMQcs0nNk/mfBZYeeLggI2mW1vikUqIK/img.png)
내부의 값은 모킹에 지정해두었던 값과 같지만 다른 시점에 생성된 객체가 내부에서 비교되고 있는 것이었다. Mock 객체를 생성해서 8870번 주소를 가진 SignInReqDto가 들어와야 미리 지정한 resDto를 반환하는데 9200번 주소를 가진 SignInReqDto가 들어오므로 null을 뿌리고 있었다.
9200번 SignInReqDto는 어디서 튀어나온거야?
![[Spring test] mockito, controller에서 null을 반환한다 - 근데 왜 이러는거야? - 9200번 SignInReqDto는 어디서 튀어나온거야? [Spring test] mockito, controller에서 null을 반환한다 - 근데 왜 이러는거야? - 9200번 SignInReqDto는 어디서 튀어나온거야?](https://blog.kakaocdn.net/dn/bMoob1/btsMP25iyAT/QHoL3exPv4ErbHYGUC71QK/img.png)
Mockito에서 post요청을 보낼 때, objectMapper에 의해 JSON stringify 된 reqDto가 Spring controller에 전달된다. Spring controller는 이 값을 @RequestBody에 의해 SigInReqDto로 래핑하여 AuthService에 넘긴다.
AuthService는 mock 객체화되어 있으므로 미리 지정한 대로 8870번 주소의 SignInReqDto를 기다리고 있지만 애먼깽뚱한 9200번 SIgnInReqDto가 들어오는 것이다.
![[Spring test] mockito, controller에서 null을 반환한다 - 근데 왜 이러는거야? - 9200번 SignInReqDto는 어디서 튀어나온거야? [Spring test] mockito, controller에서 null을 반환한다 - 근데 왜 이러는거야? - 9200번 SignInReqDto는 어디서 튀어나온거야?](https://blog.kakaocdn.net/dn/cImRgo/btsMQEifVsV/AWwWpKJqi1RvF25gsvzhm1/img.png)
별거 아닌 문제 해결이었는데 시간을 너무 많이 잡아먹혔다. 나 같은 개발자가 없기를..
'개발일기 > Spring' 카테고리의 다른 글
API versioning 방법론 (1) | 2024.11.20 |
---|---|
Spring security Architecture (3) | 2024.11.12 |
[Spring batch] meta data table을 public이 아닌 다른 schema에 생성 (1) | 2024.11.07 |
Spring batch 5.0 Migration Guide - 국문 번역 (1) | 2024.10.31 |
Spring Boot에서 JNI 사용하기(linux 환경) (5) | 2024.10.14 |
댓글