728x90
Test Slicing^1 이란 테스트를 위해 ApplicationContext를 분리하는걸 말합니다
MockMvc를 이용하여 Controller 테스트를 진행할 수 있습니다
다음과 같이 특정 컨트롤러만 테스트할 수 있습니다
@WebMvcTest(FeedControllerV2.class)
public class FeedControllerV2Test {
@WebMvcTest는 해당 컨트롤러만 Bean으로 등록되지않고, ControllerAdvice, Converter, WebMvcConfigurer, HandlerMethodArgumentResolver^2 등이 등록되며 애노테이션 주석으로 확인해볼 수 있습니다
컨트롤러 테스트의 목적은 입력검증, 출력검증입니다.
예시 테스트 코드를 보면서 어떤 것들이 가능한지 확인해보겠습니다.
@WebMvcTest(FeedControllerV2.class)
public class FeedControllerV2Test {
@Autowired
private MockMvc mockMvc;
@MockBean
private FeedServiceV2 feedServiceV2;
@Autowired
private ObjectMapper objectMapper;
@Test
@DisplayName("[성공] 피드를 조회합니다")
void getFeeds_Success() throws Exception {
FeedDtoV2 feedDto = FeedDtoTestFixtureV2.createSampleFeedDtoV2();
List<FeedDtoV2> feeds = Collections.singletonList(feedDto);
FeedSearchRequestV2 request = FeedSearchRequestV2.builder()
.memberName("yobs")
.page(1)
.size(10)
.build();
given(feedServiceV2.searchFeeds(any(FeedSearchRequestV2.class))).willReturn(feeds);
String expectedResponse = objectMapper.writeValueAsString(feeds);
mockMvc.perform(MockMvcRequestBuilders.get("/feeds/v2")
.param("memberName", request.getMemberName())
.param("page", String.valueOf(request.getPage()))
.param("size", String.valueOf(request.getSize())))
.andExpect(status().isOk())
.andExpect(content().json(expectedResponse));
}
@Test
@DisplayName("[성공] 존재하는 id로 feed 조회시 Feed를 조회합니다")
void getFeedById_Success() throws Exception {
FeedDtoV2 feedDto = FeedDtoTestFixtureV2.createSampleFeedDtoV2();
given(feedServiceV2.getFeedById(feedDto.getId())).willReturn(feedDto);
String expectedFeedDto = objectMapper.writeValueAsString(feedDto);
mockMvc.perform(MockMvcRequestBuilders.get("/feeds/v2/{id}", 1L))
.andExpect(status().isOk())
.andExpect(content().json(expectedFeedDto));
}
@Test
@DisplayName("[실패] Feed가 존재하지 않는 경우 BadRequest를 반환합니다")
void getFeedById_NotFound() throws Exception {
given(feedServiceV2.getFeedById(anyLong())).willThrow(new IllegalArgumentException("Feed not found"));
mockMvc.perform(MockMvcRequestBuilders.get("/feeds/v2/{id}", 1L))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.message").value("Feed not found"));
}
@Test
@DisplayName("[실패] 페이지 번호가 유효하지 않음")
void getFeeds_InvalidPage() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/feeds/v2")
.param("memberName", "yobs")
.param("page", "0") // Invalid page number
.param("size", "300"))
.andExpect(status().isBadRequest());
}
@Test
@DisplayName("[실패] 페이지 크기가 유효하지 않음")
void getFeeds_InvalidSize() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/feeds/v2")
.param("memberName", "yobs")
.param("page", "1")
.param("size", "300")) // Invalid size
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.message").value(String.format("size: %s", SIZE_VALIDATION_MESSAGE)));
}
}
컨트롤러에 테스트에 적용한 것들
- MockMvc를 통해 Controller의 입력 파라미터의 유효성 체크 검증 합니다
- 유효성 검증 테스트를 위해서는 ControllerAdvice를 구현하여 메시지를 비교하여 검증할 수 있습니다
- ObjectMapper와 MockMvcResultMatche.jsonPath를 통해 출력 검증이 가능합니다.
- ObjectMapper를 통해 json의 상세한 필드를 일일이 테스트 코드로 작성하여 테스트코드도 유지보수 대상이므로 더 심플한 테스트코드를 유지할 수 있습니다.
- MockBean을 통해 BDDMockito에서 지원해주는 mocking으로 controller가 가진 의존성을 격리하여 controller 기능에 집중하여 테스트합니다.
- TestFixture통해 테스트에 필요한 자원을 따로 관리하고 테스트코드 가독성을 높입니다.
참고: https://tecoble.techcourse.co.kr/post/2021-05-18-slice-test/
'스프링 프레임워크 > test' 카테고리의 다른 글
Springboot test - 3 - TestContainers (0) | 2024.08.07 |
---|---|
Springboot test - 2 - Service test with mockito (0) | 2024.08.07 |