예외처리 핸들러 테스트
- 토이프로젝트의 예외처리를 어떻게 할까 고민하다 검색과 백기선님 웹MVC강의에 예외처리 부분을 복습하였다. (회사소스에 존재하는 ExceptionHandler에는 로그만 남기도록 되어있었다.)
- MVC에서 요청을 처리하다가 에러가 발생하거나 자바에서 지원하는 예외가 발생했을 때 정의한 핸들러로 그 예외를 어떻게 처리할지?, 어떤응답을 만들지를 정의할 수 있다.
- @ExceptionHandler 어노테이션을 사용해서 정의한다.
- 처리하고 싶은 예외를 메서드 아규먼트로 선언
(+ 사용할 수 있는 메서드 아규먼트들은 문서 참조할 것 : https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-exceptionhandler) - 에러메세지, 에러페이지 등 설정 가능
@ExceptionHandler public String RuntimeExceptionHandler(RuntimeException e, Model model){ model.addAttribute("message", "runtime error"); return "error" }
- 예외처리를 핸들러를 전역 컨트롤러에 선언할 수 있다. (모든 컨트롤러에 적용됨)
// 모든 컨트롤러 적용 @ControllerAdvice("com.edu.controller") public class CommonExceptionHandler { ... } // 다수의 특정 컨트롤러 지정 가능 @ControllerAdvice(assignableTypes = {EventController.class, EventApi.class}) public class CommonExceptionHandler { ... } // 패키지 지정 가능 @ControllerAdvice("com.edu.controller") public class CommonExceptionHandler { ... }
- @RestControllerAdvice 는 @ResponseBody 가 붙은거 라고 생각하면될듯!
내가 테스트해본 것
아쉬운 점 : 그냥 잘못된 값을 던지고 해당 메서드에서 exception이 발생하였을 때 처리하는 테스트를 하였는데, 테스트 시 예외를 강제로 줘서 테스트를 하고 싶었는데 못하였음 ㅠㅠ(의미가 없으려나??)
- CommonExceptionHandler.java
@ControllerAdvice public class CommonExceptionHandler { private static final Logger logger = LoggerFactory.getLogger(CommonExceptionHandler.class); public static final String DEFAULT_ERROR_VIEW = "exception"; @ExceptionHandler(value = RuntimeException.class) public ModelAndView defaultRuntimeErrorHandler(HttpServletRequest req, RuntimeException e) { logger.debug("RuntimeException: " + e); logger.debug("url: " + req.getRequestURL()); ModelAndView mav = new ModelAndView(); mav.addObject("exception", e); mav.addObject("url", req.getRequestURL()); mav.setViewName(DEFAULT_ERROR_VIEW); return mav; } }
- LectureControllerTest.java
public class LectureControllerTest extends BaseControllerTest { /* 전역 Exception Handler 설정 CommonExceptionHandler 에서 처리하기 위한 셋팅: standaloneSetup, setControllerAdvice 지정 지정 파라미터를 다른 값으로 변경하면 CommonExceptionHandler에 안들어온다. 하지만 아예 이 부분을 제거해도 CommonExceptionHandler에 옴. */ @Before public void setUp() { this.mockMvc = MockMvcBuilders .standaloneSetup(lectureController) .setControllerAdvice(new CommonExceptionHandler()) .build(); } ... @Test @Description("Exception 발생 테스트") public void exceptionTest() throws Exception{ // Given String userId = "admin2"; // When (실패함. 어떻게 사용하는 지 더 찾아볼 것) //when(lectureController.checkedLecture(anyString(),anyString(),anyString())).thenThrow(new Exception("테스트입니다.")); // Then mockMvc.perform(post("/lecture/checkedLecture") .param("userId", userId) ).andDo(print()) .andExpect(status().is2xxSuccessful()) .andExpect(view().name("exception")) .andExpect(model().attributeExists("exception")) ; } }
Error, Exception 참고
- Error : 개발자가 미리 예측하여 처리할 수 없는 경우 (JVM 자원 부족 등의 문제?)
- 관례적으로 Error에 대해서는 Custom Class를 만들지 않는다
- Exception : 개발자가 구현한 로직에서 발생하기 때문에 미리 예측하여 처리할 수 있음
- RuntimeException
- 처리를 강제하지 않음(동작하다 exception 발생)
- 하위 Exception: NullPoint, IllegalArgument, IndexOutOfBound, System)
- Custom UnChecked Exception 을 만들 경우 RuntimeException 클래스를 상속하여 만든다.
- CheckedException
- 반드시 예외처리를 해야함(처리안하면 컴파일 오류)
- RuntimeException을 제외한 모든 클래스
- Custom checked Exception을 만들 경우 Exception 클래스를 상속하여 만든다.
- RuntimeException
- 예외처리 팁
- 아무것도안하는 에러처리는 피하기 : catch 영역에 아무 동작도 안하면 예외 발생 시 추적하기 힘들어짐. 처리를 못한다면 로그라도 남길 것
- exception.printStackTrace()는 쓰지 말 것 : 에러가 발생한 원인에 대한 Stack Trace를 추적하여 개발자에게 디버깅할 수 있는 힌트를 제공해주지만, 운영 시 성능을 생각한다면 지양해야한다. java reflection을 사용하여 trace를 추적하기 때문에 오버헤드가 발생한다고한다.
- 반복문 내에서 Checked Exception에 대한 처리는 지양할 것 : 반복문 내에서 Checked Exception에 대한 예외처리 구문이 들어가게 되면 성능은 2배 3배 떨어지게 된다.
- @ControllerAdvice 테스트참고
- Exception 참고