관리 메뉴

bright jazz music

blog03: 데이터 검증2 (ExceptionController, @ControllerAdvice) 본문

Projects/blog

blog03: 데이터 검증2 (ExceptionController, @ControllerAdvice)

bright jazz music 2022. 12. 13. 08:38

이전 포스팅처럼 에러를 처리한다면 아래와 같은 문제가 있다.

/*
 *  1. 매번 메서드마다 검증해야함. 귀찮음. 실수 가능성+
 *  2. 응답값을 HashMap으로 하지 말고 응답 클래스로 만든 것이 좋다.
 *  3. 여러 개의 에러처리가 힘들다. 여러 개의 응답을 보내줘야 한다면 에러마다 만들어줘야 한다...
 *  4. 반복작업을 피할 것
 *
 *  @ControllerAdvice를 사용하여 이런 문제를 개선해 보자
 * : 클래스에 이 어노테이션을 달면 개별 컨트롤러가 아니라 모든 컨트롤러에 대한 에러를 캐치한다.
 */

 

----------------------------------------------------------------

전역적인 에러를 처리할 임의의 컨트롤러 생성(여기서는 ExceptionController) ==>

생성한 컨트롤러(ExceptionController)에 @ControllerAdvice 어노테이션 부착 ==>

처리할 @ResponseStatus를 사용하여 처리할 status 설정

@ExceptionHandler(Exception.class)를 붙인 exceptionHandler() 메소드 사용

exceptionHandler() 메소드에서 처리

 

----------------------------------------------------------------

 

 

 

 

1. @ControllerAdvice를 적용하는 ExceptionController 생성

 

Exception을 처리하기 위한 클래스 생성. ( ExceptionController.java)

그리고 클래스에 @ControllerAdvice 적용

 

//ExceptionController.java

package com.endofma.blog.controller;

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.ResponseStatus;

@ControllerAdvice
public class ExceptionController {

    @ResponseStatus(HttpStatus.OK) //여기선 일부러 200으로 설정하였다.
    @ExceptionHandler(Exception.class)
    public void exceptionHandler(){
        System.out.println("하하하 ExceptionController.java");
    }
}

 

위의 System.out.println("하하하 ExceptionController.java");에 브레이크 포인트를 걸고 디버그 테스트.
//PostControllerTest.java

@WebMvcTest
class PostControllerTest {
    @Autowired
    private MockMvc mockMvc; ////Could not autowire. No beans of 'MockMvc' type found.

	//...

//PostControllerTest.java

    @Test
    @DisplayName("/posts 요청시 title 값은 필수다")
    void test2() throws Exception {
        //expected

        mockMvc.perform(post("/posts")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"title\": null, \"content\": \"내용입니다.\"}") //title 값을 빈 스트링으로 설정하였다.
                )
                .andExpect(status().isOk())
//                .andExpect(content().string(""))
                .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("타이틀을 입력하세요!")) //json 검증. json 응답값의 필드 값이 value로 내려 오는지 여부 확인
                .andDo(print());
    }
}

 

그러나 걸리지 않는다. post메서드의 파라미터에 BindingResult가 존재하기 때문이다.

이를 제거하고, 그 밑의 코드들도 다 지워준다.

//PostController.java

@Slf4j
@RestController
public class PostController {

    @PostMapping("/posts")
    public Map<String, String> post(@RequestBody @Valid PostCreate params) {/
    //파라미터에서 BindingResult 제거.
        
        //이미 바인딩에서 에러 발생. 여기까지 오지 못함.
        log.info("params={}", params.toString());
        return Map.of();
    }
}

 

다시 테스트 해본다.

 

그러면 아래와 같이 ExceptionController 클래스가 대신 에러를 처리하는 모습을 볼 수 있다. (하하하 ExceptionController.java)

 

또한 @ResponseStatus(HttpStatus.OK) 덕분에 status = 200으로 응답 받은 것을 확인할 수 있다.

2022-12-19 18:47:28.145  INFO 12392 --- [    Test worker] c.e.blog.controller.PostControllerTest   : Started PostControllerTest in 2.665 seconds (JVM running for 5.297)
하하하 ExceptionController.java

MockHttpServletRequest:
      HTTP Method = POST
      Request URI = /posts
       Parameters = {}
          Headers = [Content-Type:"application/json;charset=UTF-8", Content-Length:"46"]
             Body = {"title": null, "content": "내용입니다."}
    Session Attrs = {}

Handler:
             Type = com.endofma.blog.controller.PostController
           Method = com.endofma.blog.controller.PostController#post(PostCreate)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = org.springframework.web.bind.MethodArgumentNotValidException

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = []
     Content type = null
             Body = 
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

No value at JSON path "$.title"
java.lang.AssertionError: No value at JSON path "$.title"
	at org.springframework.test.util.JsonPathExpectationsHelper.evaluateJsonPath(JsonPathExpectationsHelper.java:304)

	... 89 more


Disconnected from the target VM, address: 'localhost:58898', transport: 'socket'
PostControllerTest > /posts 요청시 title 값은 필수다 FAILED
    java.lang.AssertionError at PostControllerTest.java:58
        Caused by: java.lang.IllegalArgumentException at PostControllerTest.java:58
1 test completed, 1 failed
> Task :test FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':test'.
> There were failing tests. See the report at: file:///D:/personal/work/blog/build/reports/tests/test/index.html
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
* Get more help at https://help.gradle.org
BUILD FAILED in 23s
4 actionable tasks: 2 executed, 2 up-to-date

 

2. 에러에 대한 응답을 json으로 반환하기

 

json으로 응답하기

//ExceptionController.java

@Slf4j
@ControllerAdvice
public class ExceptionController {

    @ResponseStatus(HttpStatus.BAD_REQUEST) //400
    @ResponseBody //응답을 json으로 보내기 위해 추가
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Map<String, String> invalidRequestHandler(MethodArgumentNotValidException e){
        //MethodArgumentNotValidation
        //e.getField()를 사용하기 위해서는 Exception이 아니라 MethodArgumentNotValidation을 사용해야 한다. 파라미터 변경 필요
        FieldError fieldError = e.getFieldError();
        String field = fieldError.getField();
        String message = fieldError.getDefaultMessage();

        Map<String, String > response = new HashMap<>();
        response.put(field, message);
        return response;
    }
}

 

 

 

Comments