프로젝트를 진행하면서 여러 테스트 코드들을 작성했다. 문제는 MockMVC를 사용해 컨트롤러 테스트를 작성할 때 발생했는데, 컨트롤러 실패 테스트에서 예외를 던지고 그에 맞는 HttpStatus를 발생시키는지 확인해야했었고 계속 제대로 발생시키지 못하는 문제가 있었다.
먼저 구성된 코드를 보자. CreatorNotFoundException, @RestControllerAdvice가 있는 GlobalExceptionHandler, CreatorService가 있다.
CreatorNotFoundException은 발생시킬 예외이다. MESSAGE와 Httpstatus를 가지고 있고 BusinessException을 상속한다.
import org.springframework.http.HttpStatus;
public class CreatorNotFoundException extends BusinessException {
private static final String MESSAGE = "크리에이터를 찾을 수 없습니다.";
private static final HttpStatus status = HttpStatus.NOT_FOUND;
public CreatorNotFoundException() {
super(MESSAGE, status);
}
}
GlobalExceptionHandler를 보자. BusinessException이 발생했을 때 이를 잡아서 아래와 같이 반환하는 코드이다.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
protected ResponseEntity<ErrorResponse> handledException(BusinessException e) {
log.info(e.getMessage(), e);
HttpStatus httpStatus = e.getStatus();
ErrorResponse errorResponse = new ErrorResponse(e.getMessage());
return ResponseEntity.status(httpStatus).body(errorResponse);
}
}
CreatorService를 보자. CreatorController는 삽입하지 않았지만 이 컨트롤러에서 deleteCreator 메서드를 호출하고 있다.
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class CreatorService {
private final CreatorRepository creatorRepository;
@Transactional
public void deleteCreator(Long creatorId) {
try {
creatorRepository.deleteById(creatorId);
} catch(EmptyResultDataAccessException e) {
throw new CreatorNotFoundException();
}
}
}
ControllerTest 코드를 보자. 아래 코드처럼 `creatorService`를 Mocking하고 무조건 CreatorNotFoundException을 발생시키도록 했다. 그리고 그에 따른 응답으로 404 NotFound와 CreatorNotFoundException이 발생하는지 확인하는 테스트이다.
@WebMvcTest(CreatorController.class)
@AutoConfigureMockMvc
public class CreatorControllerTest {
@MockBean
CreatorService creatorService;
@Autowired
MockMvc mockMvc;
@Autowired
ObjectMapper objectMapper;
...생략...
@Nested
@DisplayName("유효하지 않은 크리에이터 id가 들어오면")
class Fail {
@Test
@DisplayName("해당 크리에이터를 삭제하지 않고 NOT FOUND를 응답한다")
void deleteCreatorReturnNotFound() throws Exception {
Long creatorId = 1L;
doThrow(CreatorNotFoundException.class).when(creatorService).deleteCreator(creatorId);
mockMvc.perform(delete("/admin/creators/" + creatorId)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isNotFound())
.andExpect(response -> Assertions.assertTrue(
response.getResolvedException() instanceof CreatorNotFoundException))
.andDo(print());
}
}
}
결론부터 말하자면 이 테스트는 실패한다. 처음엔 왜 이럴까 고민을 많이했다. 분명히 Service에서 Mocking하여 예외를 발생시키도록했고 이를 호출하면 예외가 발생되어야하는거 아닌가 싶었다. Service코드에서 예외를 발생시키는 것말고 아무것도 하지 않기 때문이다.
위 이미지들처럼 CreatorNotFoundException이 아무것도 들고 있지 않았다.. 그래서 Exception 내부에 있던 HttpStatus도 null이고 MESSAGE 또한 null이었다. 원인은 doThrow문 안에 있는 CreatorNotFoundException.class 였다. 알아보니 Mocking 과정에서 .class를 사용할 것이 아니라 new CreatorNotFoundException을 발생시켰으면 해결될 문제였다.
따라서 다음과 바꿔서 테스트 코드를 작성했고 결과는 다음과 같이 성공했다. 결론은 CustomException을 만들고 그 안에서 필드값을 두고 사용하려면 기본적으론 new 키워드를 통해 직접 생성해 Mocking하고 그러지 않아도 될 경우에만 .class 쓰면 이런 문제를 겪지 않을 것 같다.
@Test
@DisplayName("해당 크리에이터를 삭제하지 않고 NOT FOUND를 응답한다")
void deleteCreatorReturnNotFound() throws Exception {
Long creatorId = 1L;
doThrow(new CreatorNotFoundException()).when(creatorService).deleteCreator(creatorId);
mockMvc.perform(delete("/admin/creators/" + creatorId)
.header(HttpHeaders.AUTHORIZATION, authorizationHeader)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isNotFound())
.andExpect(response -> Assertions.assertTrue(
response.getResolvedException() instanceof CreatorNotFoundException))
.andDo(print());
}
}
참고
'개인 공부' 카테고리의 다른 글
CS 공부 - Spring (0) | 2023.03.31 |
---|---|
CS 공부 - HTTP (0) | 2023.03.24 |
당근마켓 클론코딩 - 게시글 검색 기능 구현을 위한 실험 (1) | 2023.02.08 |
정적 팩토리 메서드 (0) | 2022.11.21 |
Logback.xml 동일 패키지 내 다른 레벨 처리 (2) | 2022.11.10 |