개요
프로젝트에 대한 테스트를 작성하던 중 List로 반환되는 DTO에 대한 검증에 대한 코드가 길어져 해결할 방법이 있을까 찾아보았다.
기존에 mockMvc.perform()의 체이닝 메소드인 .andExpect를 사용해서 json의 값을 하나하나 비교해주었다.
검증할 DTO가 많아질 경우 다음과 같이 코드가 너무 길어졌다.
@Test
@DisplayName("read all post test")
public void readAllPostTest() throws Exception {
PostDTO after1 = PostDTO.entityToDTO(PostEntity.initEntity(post1, UserEntity.builder().email(post1.getEmail()).build()));
after1.setId(1L);
PostDTO after2 = PostDTO.entityToDTO(PostEntity.initEntity(post2, UserEntity.builder().email(post2.getEmail()).build()));
after2.setId(2L);
PostDTO after3 = PostDTO.entityToDTO(PostEntity.initEntity(post3, UserEntity.builder().email(post3.getEmail()).build()));
after3.setId(3L);
List<PostDTO> postDTOList = Arrays.asList(after1, after2, after3);
//given
given(postService.readAllPost())
.willReturn(postDTOList);
//when
mockMvc.perform(
get("/post")
.contentType(MediaType.APPLICATION_JSON)
)
.andExpect(status().isOk())
// after1
.andExpect(jsonPath("$[0].id").value(after1.getId()))
.andExpect(jsonPath("$[0].title").value(after1.getTitle()))
.andExpect(jsonPath("$[0].content").value(after1.getContent()))
.andExpect(jsonPath("$[0].email").value(after1.getEmail()))
.andExpect(jsonPath("$[0].like").value(after1.getLike()))
.andExpect(jsonPath("$[0].views").value(after1.getViews()))
// after2
.andExpect(jsonPath("$[1].id").value(after2.getId()))
.andExpect(jsonPath("$[1].title").value(after2.getTitle()))
.andExpect(jsonPath("$[1].content").value(after2.getContent()))
.andExpect(jsonPath("$[1].email").value(after2.getEmail()))
.andExpect(jsonPath("$[1].like").value(after2.getLike()))
.andExpect(jsonPath("$[1].views").value(after2.getViews()))
// after3
.andExpect(jsonPath("$[2].id").value(after3.getId()))
.andExpect(jsonPath("$[2].title").value(after3.getTitle()))
.andExpect(jsonPath("$[2].content").value(after3.getContent()))
.andExpect(jsonPath("$[2].email").value(after3.getEmail()))
.andExpect(jsonPath("$[2].like").value(after3.getLike()))
.andExpect(jsonPath("$[2].views").value(after3.getViews()))
.andDo(print())
//then
verify(postService).readAllPost();
}
이렇게 하지 않고 AssertJ를 통해서 Response값을 비교하는 게 어떨까 생각하게 되어 찾아보았다.
과정
MvcResult result = mockMvc.perform(get("/post"))
.andExpect(status().isOk())
.andDo(print()).andReturn();
먼저 mockMvc의 결과값은 .andReturn()
을 통해 MvcResult 객체를 받아올 수 있다.
MvcResult는 Spring MVC에 대한 인터셉터, 플래시맵과 같은 값을 가져올 수 있다.
.andDo(print())
: 에서 나오는 정보들을 가져올 수 있다.
MvcResult
public interface MvcResult {
MockHttpServletRequest getRequest();
MockHttpServletResponse getResponse();
@Nullable
Object getHandler();
@Nullable
HandlerInterceptor[] getInterceptors();
@Nullable
ModelAndView getModelAndView();
@Nullable
Exception getResolvedException();
FlashMap getFlashMap();
Object getAsyncResult();
Object getAsyncResult(long timeToWait);
}
org.springframework.test.web.servlet.MvcResult
DefaultMvcResult
final MvcResult mvcResult = new DefaultMvcResult(request, mockResponse);
MockMvc에서 MvcResult의 구현체로 DefaultMvcResult를 사용하고 있었다.
class DefaultMvcResult implements MvcResult {
private final MockHttpServletResponse mockResponse;
@Override
public MockHttpServletResponse getResponse() {
return this.mockResponse;
}
}
DefaultMvcResult는 MockHttpServletResponse라는 Http요청 테스트에 대한 응답을 가지고 있었다.
우리의 목표인 HTTP Response의 body값을 직접 비교해주기 위해서 사용할 메소드는 getResponse()
로 HttpServletResponse 인터페이스의 구현체인 MockHttpServletResponse를 반환한다.
ObjectMapper
이제 이 MockHttpServletResponse의 getContentAsString()
메소드를 이용하여 응답 바디값을 가져오고 이 값을 List로 매핑해주기 위해서 아래와 같이 해줄 수 있다.
List<PostDTO> after = mapper.readValue(
result.getResponse().getContentAsString()
, new TypeReference<List<PostDTO>>() {});
readValue()
: JSON 문자열을 지정된 타입에 맞게 역직렬화하는 메소드
result.getResponse().getContentAsString()
: MockMvc의 HTTP 테스트 결과 응답 body값을 String 형태로 반환new TypeReference<List<PostDTO>>() {}
: readValue()의 역직렬화할 타입을 명시하기 위한 객체- 익명 클래스의 형태로 전달하며 여기서는 List 형태로 역직렬화하도록 타입을 명시해주었다.
Assertion
마지막으로 assertJ로 동등성 비교를 통해서 검증해준다.
assertThat(after).usingRecursiveComparison().isEqualTo(postDTOList);
usintRecursiveComparsion()
: 객체 내부의 구조와 값을 비교할 때 사용한다. 즉, 동등성 비교가 가능해진다.
결론
MockMvc의 .andExpect()를 통해 값을 하나하나 비교하는 것보다 훨씬 짧은 코드로 구현할 수 있었다.
'Spring' 카테고리의 다른 글
[Spring Security] 스프링 부트 Access Token에서 Refresh Token추가하여 구현하기 (0) | 2024.03.27 |
---|---|
[JPA] 영속성 컨텍스트 (0) | 2024.03.04 |
[Spring Boot] 환경변수로 .jar와 docker run 프로파일 결정 (0) | 2024.02.05 |
[Spring JPA] 일대다 연관관계 매핑 (0) | 2024.02.04 |
[Spring] ControllerAdvice, RestControllerAdvice를 통한 예외 처리 (0) | 2024.02.02 |