-
[Spring Boot] REST API 만들기(13) - 예외 처리 및 테스트Spring Boot/2.4.x - REST API 만들기 2020. 5. 21. 16:48반응형
스프링 MVC에서는 애플리케이션에서 발생하는 예외를 처리할 때는 @ExceptionHandler 어노테이션과 @ControllerAdvice 어노테이션을 사용합니다. @ExceptionHandler은 전체 애플리케이션의 예외 처리를 하지 못하기 때문에, 전체 애플리케이션의 예외 처리가 가능하고, ReponseEntity 형식을 사용할 수 있는 @ControllerAdvice 어노테이션을 이용해서 예외 처리를 하도록 하겠습니다.
1. ResourceNotFoundException.java 추가
자원을 찾을 수 없다는 예외를 처리하기 위해 com.spring.board.exception 패키지를 생성한 후 ResourceNotFoundException 클래스를 추가하세요.
ResourceNotFoundException.java
더보기12345package com.api.board.exception;public class ResourceNotFoundException extends RuntimeException {}cs 2. BoardController.java 수정
게시글 상세 조회 시 값이 null인 경우 ResourceNotFoundException을 발생하도록 수정하세요.
BoardController.java
더보기12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788package com.api.board.controller;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.HttpStatus;import org.springframework.web.bind.annotation.DeleteMapping;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.PutMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseStatus;import org.springframework.web.bind.annotation.RestController;import com.api.board.domain.Board;import com.api.board.domain.Boards;import com.api.board.exception.ResourceNotFoundException;import com.api.board.service.BoardService;@RequestMapping("/board")@RestControllerpublic class BoardController {@Autowiredprivate BoardService boardService;/** 게시글 목록 조회 */@GetMappingpublic Boards getBoardList() throws Exception {Boards boards = new Boards();boards.setBoards(boardService.getBoardList());return boards;}/** 게시글 상세 조회 */@GetMapping("/{board_seq}")public Board getBoardDetail(@PathVariable("board_seq") int board_seq) throws Exception {Board board = boardService.getBoardDetail(board_seq);if(board == null) {throw new ResourceNotFoundException();}return board;}/** 게시글 등록 */@ResponseStatus(value = HttpStatus.CREATED)@PostMappingpublic Board insertBoard(@RequestBody Board board) throws Exception {boardService.insertBoard(board);int boardSeq = board.getBoard_seq();Board boardDetail = boardService.getBoardDetail(boardSeq);return boardDetail;}/** 게시글 수정 */@ResponseStatus(value = HttpStatus.OK)@PutMapping("/{board_seq}")public Board updateBoard(@PathVariable("board_seq") int board_seq, @RequestBody Board board) throws Exception {boardService.updateBoard(board);Board boardDetail = boardService.getBoardDetail(board_seq);return boardDetail;}/** 게시글 삭제 */@ResponseStatus(value = HttpStatus.OK)@DeleteMapping("/{board_seq}")public Board deleteBoard(@PathVariable("board_seq") int board_seq) throws Exception {boardService.deleteBoard(board_seq);Board deleteBoard = new Board();deleteBoard.setBoard_seq(board_seq);return deleteBoard;}}cs 3. ResponseError.java 추가
REST 서비스에서는 HTTP 상태 코드를 가지고 결과 값을 판단하기 때문에 앞에서 구현한 것 처럼 간단하게 에러 결과를 처리할 수 있습니다. 하지만 요청이 JSON 형식이면 에러 결과도 JSON 형식으로 보여주는 것이 좋기 때문에 에러 결과를 처리하기 위해 com.spring.board.exception.domain 패키지를 생성한 후 ResponseError 클래스를 생성하세요. XML 형식으로도 사용하기 위해서 @XmlRootElement 어노테이션 클래스도 추가하세요.
ResponseError.java
더보기12345678910111213141516171819202122232425262728293031323334package com.api.board.exception.domain;import javax.xml.bind.annotation.XmlRootElement;@XmlRootElement(name = "error")public class ResponseError {private int code;private String message;public ResponseError() {}public ResponseError(int code, String message) {this.code = code;this.message = message;}public int getCode() {return code;}public void setCode(int code) {this.code = code;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}}cs 4. ResponseEntityExceptionHandler.java 추가
com.spring.board.exception.handle 패키지를 생성한 후 ResponseEntityExceptionHandler 클래스를 추가하세요.
@ControllerAdvice 어노테이션을 클래스에 선언하고, @ExceptionHandler 어노테이션을 사용해서 처리할 예외를 정한 다음에 @ResponseBody 어노테이션을 이용해서 응답 결과를 반환하세요.
ResponseEntityExceptionHandler.java
더보기12345678910111213141516171819202122package com.api.board.domain.handle;import org.springframework.http.HttpStatus;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.ResponseStatus;import com.api.board.exception.ResourceNotFoundException;import com.api.board.exception.domain.ResponseError;@ControllerAdvicepublic class ResponseEntityExceptionHandler {@ExceptionHandler(value = { ResourceNotFoundException.class })@ResponseStatus(value = HttpStatus.NOT_FOUND)@ResponseBodypublic ResponseError handleResourceNotFound(ResourceNotFoundException e) {ResponseError responseError = new ResponseError(404, "Resource가 존재하지 않습니다.");return responseError;}}cs 5. WebMvcConfig.java 수정
예외를 처리하는 ResponseEntityExceptionHandler 클래스는 @ControllerAdvice 어노테이션을 사용하기 때문에 WebMvcConfig 설정에서 ResponseEntityExceptionHandler 클래스를 자동으로 스캔하도록 @ComponentScan의 includeFilters 부분에 ControllerAdvice를 추가하세요.
WebMvcConfig.java
더보기123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172package com.api.board.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.ComponentScan.Filter;import org.springframework.context.annotation.Configuration;import org.springframework.http.MediaType;import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;import org.springframework.http.converter.xml.MarshallingHttpMessageConverter;import org.springframework.oxm.jaxb.Jaxb2Marshaller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import com.api.board.interceptor.BoardInterceptor;import com.fasterxml.jackson.databind.DeserializationFeature;import com.fasterxml.jackson.databind.ObjectMapper;import com.fasterxml.jackson.databind.SerializationFeature;import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule;@ComponentScan(basePackages = {"com.api.board.controller"}, useDefaultFilters = false, includeFilters = {@Filter(Controller.class), @Filter(ControllerAdvice.class)})@Configurationpublic class WebMvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new BoardInterceptor()).addPathPatterns("/**").excludePathPatterns("/sample/**");}@Beanpublic MappingJackson2HttpMessageConverter mappingJacksonHttpMessageConverter() {MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();ObjectMapper objectMapper = converter.getObjectMapper();objectMapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);JaxbAnnotationModule module = new JaxbAnnotationModule();objectMapper.registerModule(module);return converter;}@Beanpublic MarshallingHttpMessageConverter marshallingHttpMessageConverter() {MarshallingHttpMessageConverter converter = new MarshallingHttpMessageConverter();converter.setMarshaller(jaxb2Marshaller());converter.setUnmarshaller(jaxb2Marshaller());return converter;}@Beanpublic Jaxb2Marshaller jaxb2Marshaller() {Jaxb2Marshaller marshaller = new Jaxb2Marshaller();marshaller.setPackagesToScan(new String[] { "com.api.board.domain" });return marshaller;}@Overridepublic void configureContentNegotiation(ContentNegotiationConfigurer configurer) {configurer.favorParameter(true).ignoreAcceptHeader(false).defaultContentType(MediaType.APPLICATION_JSON).mediaType("json", MediaType.APPLICATION_JSON).mediaType("xml", MediaType.APPLICATION_XML);}}cs 6. 예외 테스트
ResponseEntityExceptionHandler가 정상적으로 작동하는지 테스트하기 위해 존재하지 않는 게시글 상세 조회 API를 호출 시, ResourceNotFoundException이 발생하고 ResponseEntityExceptionHandler 클래스의 handleResourceNotFound 메소드가 실행되어 404 코드가 응답되는지 확인하세요.
BoardControllerExceptionTest.java
더보기123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384package com.api.board.controller;import static org.junit.Assert.assertEquals;import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;import org.junit.Before;import org.junit.Test;import org.junit.runner.RunWith;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.http.MediaType;import org.springframework.test.context.junit4.SpringRunner;import org.springframework.test.context.web.WebAppConfiguration;import org.springframework.test.web.servlet.MockMvc;import org.springframework.test.web.servlet.MvcResult;import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;import org.springframework.test.web.servlet.setup.MockMvcBuilders;import org.springframework.web.context.WebApplicationContext;import org.springframework.web.filter.CharacterEncodingFilter;@WebAppConfiguration@RunWith(SpringRunner.class)@SpringBootTestpublic class BoardControllerExceptionTest {Logger logger = LoggerFactory.getLogger(BoardControllerExceptionTest.class);private MockMvc mockMvc;@AutowiredWebApplicationContext webApplicationContext;@Beforepublic void setUp() {mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).addFilters(new CharacterEncodingFilter("UTF-8", true)).alwaysDo(print()).build();}int boardSeq = 0;/** 게시글 상세 조회 시 응답 값이 404이면 테스트 통과 */@Testpublic void getBoardDetailJSON(){try {if (boardSeq != 0) {MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/board/" + boardSeq).accept(MediaType.APPLICATION_XML)).andReturn();assertEquals(404, mvcResult.getResponse().getStatus());}} catch (Exception e) {e.printStackTrace();}}/** 게시글 상세 조회 시 응답 값이 404이면 테스트 통과 */@Testpublic void getBoardDetailXML() throws Exception {try {if (boardSeq != 0) {MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/board/" + boardSeq).accept(MediaType.APPLICATION_JSON)).andReturn();assertEquals(404, mvcResult.getResponse().getStatus());}} catch (Exception e) {e.printStackTrace();}}}cs 소스 코드는 Github Repository - https://github.com/tychejin1218/api-board_v1 (branch : section13) 를 참조하세요.
Github에서 프로젝트 가져오기 - https://tychejin.tistory.com/33반응형'Spring Boot > 2.4.x - REST API 만들기' 카테고리의 다른 글
[Spring Boot] REST API 만들기(15) - Lombok 적용 (0) 2020.06.03 [Spring Boot] REST API 만들기(14) - Swagger 적용 (0) 2020.05.22 [Spring Boot] REST API 만들기(12) - Content Negotiation 설정 (0) 2020.05.20 [Spring Boot] REST API 만들기(11) - JSON Root Element 추가 (0) 2020.05.20 [Spring Boot] REST API 만들기(10) - XML 요청/응답 구현 및 테스트 (0) 2020.05.19