[Spring Plus] Level 1-4 요구사항 반영

2024. 11. 13. 10:42내일배움캠프/Spring Plus

 'Level 1-4' 의 요구사항을 반영한 내용을 기록한 포스팅이다. 어떠한 생각과 과정을 통해 요구사항을 반영했는지 알 수 있도록 작성해 보았다.

 

0. 요구사항

 테스트 패키지 'org.example.expert.domain.todo.controller.TodoControllerTest' 의 테스트가 실패하고 있다. 해당 테스트가 정상적으로 수행되어 통과할 수 있도록 테스트 코드를 수정해야 한다.

 

 

1. 문제 원인 파악

 요구사항에서 말한 테스트는 일정 조회(단건)시 해당 일정이 존재하지 않는 일정일 경우의 응답을 확인하는 코드였다. 하지만 아래와 같이 어색한 부분이 보였는데 바로 'then' 부분의 응답 값을 확인하는 부분이었다.

@WebMvcTest(TodoController.class)
class TodoControllerTest {
    @Autowired
    private MockMvc mockMvc;
    @MockBean
    private TodoService todoService;
    
    ...
    
    @Test
    void todo_단건_조회_시_todo가_존재하지_않아_예외가_발생한다() throws Exception {
        ...
        
        // then
        mockMvc.perform(get("/todos/{todoId}", todoId))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.status").value(HttpStatus.OK.name()))
                .andExpect(jsonPath("$.code").value(HttpStatus.OK.value()))
                .andExpect(jsonPath("$.message").value("Todo not found"));
    }
}

 

분명 에러(예외)가 발생했다면 'HttpStatus.OK' 를 응답 상태로 반환하지 않을 것 같은데 해당 상태가 지정된 것이다. 그래서 바로 'TodoService.getTodo()' 를 확인해 봤다.

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class TodoService {

    private final TodoRepository todoRepository;
    private final WeatherClient weatherClient;

    ...

    public TodoResponse getTodo(long todoId) {
        Todo todo = todoRepository.findByIdWithUser(todoId)
                .orElseThrow(() -> new InvalidRequestException("Todo not found"));

        ...
        );
    }
}

 

'getTodo()' 의 경우 전달받은 일정 ID 로 일정을 조회하는 로직을 가지고 있었는데, 만약 일정 ID 에 해당하는 일정이 DB 에 존재하지 않을 경우 'InvalidRequestException' 을 던진다. 이후 프로젝트를 더 살펴보니 'GlobalExceptionHandler' 클래스가 있었고 클래스 네임을 통해 해당 클래스가 프로젝트 전역의 예외를 처리(=핸들링)하는 역할을 한다고 판단 살펴보았다.

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(InvalidRequestException.class)
    public ResponseEntity<Map<String, Object>> invalidRequestException(InvalidRequestException ex) {
        HttpStatus status = HttpStatus.BAD_REQUEST;
        return getErrorResponse(status, ex.getMessage());
    }

    ...

    public ResponseEntity<Map<String, Object>> getErrorResponse(HttpStatus status, String message) {
        Map<String, Object> errorResponse = new HashMap<>();
        errorResponse.put("status", status.name());
        errorResponse.put("code", status.value());
        errorResponse.put("message", message);

        return new ResponseEntity<>(errorResponse, status);
    }
}

 

예상대로 'GlobalExceptionHandler' 클래스는 프로젝트에서 발생할 수 있는 여러 예외를 처리하고 있었고 위에서 언급한 'InvalidRequestException' 또한 'invalidRequestException()' 을 통해 처리하고 있었다.

 

해당 메서드는 개발자가 던진 예외와 지정한 상태(HttpStatus) 를 파라미터로 전달해 'getErrorResponse()' 를 호출한다. 'getErrorResponse()' 는 ResponseBody 를 만들고 지정한 HttpStatus 와 함께 응답을 생성해 반환하는데, 이것을 'invalidRequestException()' 에서 반환받아 응답으로 반환한다고 이해했다.

 

그래서 결국 'InvalidRequestException' 을 처리했다면 반환하는 응답의 상태는 'BAD_REQUEST(400)' 일 것이고 ResponseBody 에 담기는 정보는 { status = BAD_REQUEST, code = 400, message = "Todo not found" } 가 될 것이다. 그러니 테스트 코드에서도 해당 정보가 맞는지를 확인하면 테스트가 정상적으로 통과되지 않을까 싶다.

 

참고 : 'GlobalExceptionHandler' 의 'invalidRequestException()' 이 기존에는 'invalidRequestExceptionException()' 으로 작성되어 있어 수정해 둠

 

 

2. 문제 해결

 파악한 내용으로 수정한 테스트 코드는 아래와 같다.

@WebMvcTest(TodoController.class)
class TodoControllerTest {
    ...

    @Test
    void todo_단건_조회_시_todo가_존재하지_않아_예외가_발생한다() throws Exception {
        // given
        long todoId = 1L;

        // when
        when(todoService.getTodo(todoId))
                .thenThrow(new InvalidRequestException("Todo not found"));

        // then
        mockMvc.perform(get("/todos/{todoId}", todoId))
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.status").value(HttpStatus.BAD_REQUEST.name()))
                .andExpect(jsonPath("$.code").value(HttpStatus.BAD_REQUEST.value()))
                .andExpect(jsonPath("$.message").value("Todo not found"));
    }
}

 

'then' 에 대한 부분만 수정하였다. 해당 API 를 요청할 경우 만약 일정이 존재하지 않는다면 상태는 'HttpStatus.BAD_REQUEST', ResponseBody 는 HTTP 상태 코드 이름(=BAD_REQUEST), 값(=400)을 갖는지 확인하도록 위와 같이 수정해 주었다. 이후 테스트를 실행해보니 아래와 같이 정상적으로 통과하는 것을 확인할 수 있었다.

테스트 수정 후 테스트 결과

 

마지막으로 이전에 '1-2 요구사항' 을 반영하며 User 생성자 또한 수정한 적이 있었는데, 해당 생성자를 사용하는 테스트가 작성되어 있는지 몰랐었다. 그래서 'todo_단건_조회에_성공한다' 테스트가 제대로 통과되지 못하고 있었다. 그래서 해당 테스트의 코드 또한 이전에 반영한 요구사항에 맞춰 수정하였고, 결과적으로 위와 같이 TodoControllerTest 의 모든 테스트가 통과하게 되었다.