본문 바로가기

Java/SpringBoot

[SpringBoot] Test(2) MockMvc를 이용해서 테스트하기(@WebMvcTest, @AutoConfigureMockMvc)

Mock

- Mock이라는 단어를 사전에서 찾아보면 '테스트를 위해 만든 모형'을 의미한다.

- 테스트를 위해 실제 객체와 비슷한 모의 객체를 만드는 것을 모킹(Mocking)이라고 하며, 모킹한 객체를 메모리에서 얻어내는 과정을 목업(Mock-up)이라고 한다.

 

- 객체를 테스트하기 위해서는 당연히 테스트 대상 객체가 메모리에 있어야 한다.하지만 생성하는 데 복잡한 절차가 필요하거나 많은 시간이 소요되는 객체는 자주 테스트하기 어렵다. 또는 웹 애플리케이션의 컨트롤러처럼 WAS나 다른 소프트웨어의 도움이 반드시 필요한 객체도 있을 수 있다. 이런 복잡한 객체는 당연히 테스트 과정도 복잡하고 어려울 수 밖에 없다.

 

- 따라서 테스트 하려는 실제 객체와 비슷한 가짜 객체를 만들어서 테스트에 필요한 기능만 가지도록 모킹을 하면 테스트가 쉬워진다.

- 테스트하려는 객체가 복잡한 의존성을 가지고 있을 때, 모킹한 객체를 이용하면, 의존성을 단절시킬 수 있어서 쉽게 테스트할 수 있다.

- 웹 애플리케이션에서 컨트롤러를 테스트할 때, 서블릿 컨테이너를 모킹하기 위해서는 @WebMvcTest를 사용하거나 @AutoConfigureMockMvc를 사용하면 된다.

 

- 웹 환경에서 컨트롤러를 테스트하려면 반드시 서블릿 컨테이너가 구동되고 DispatcherServlet 객체가 메모리에 올라가야 하지만, 서블릿 컨테이너를 모킹하면 실제 서블릿 컨테이너가 아닌 테스트용 모형 컨테이너를 사용하기 때문에 간단하게 컨트롤러를 테스트할 수 있다.

 


Mock 객체로 테스트하기

1) @WebMvcTest 사용하기

- 웹에서 테스트하기 힘든 컨트롤러를 테스트하는데 적합한다.

- 웹상에서 요청과 응답에 대해 테스트할 수 있을 뿐만 아니라 시큐리티 혹은 필터까지 자동으로 테스트하며 수동으로 추가/삭제까지 가능하다.

 

@WebMvcTest

- @Controller, @RestController가 설정된 클래스들을 찾아 메모리에 생성한다. 

- @Service나 @Repository가 붙은 객체들은 테스트 대상이 아닌 것으로 처리되기 때문에 생성되지 않는다.

- @WebMvcTest가 설정된 테스트 케이스에서는 서블릿 컨테이너를 모킹한 MockMvc타입의 객체를 목업하여 컨트롤러에 대한 테스트코드를 작성할 수 있다.

- @WebMvcTest 어노테이션을 사용하면 MVC 관련 설정인 @Controller, @ControllerAdvice, @JsonComponent와 Filter, WebMvcConfigurer,  HandlerMethodArgumentResolver만 로드되기 때문에, 실제 구동되는 애플리케이션과 똑같이 컨텍스트를 로드하는 @SpringBootTest 어노테이션보다 가볍게 테스트 할 수 있다.

 

private MockMvc mvc

 - 웹 API를 테스트할 떄 사용한다.

 - 스프링 MVC 테스트의 시작점이다.

 - 이 클래스를 통해 HTTP GET, POST 등에 대한 API 테스트를 할 수 있다.

 

mockMvc.perfom(get("/hello"))

 - MockMvc를 통해 /hello 주소로 HTTP GET을 요청한다.

 - 체이닝이 지원되어 아래와 같이 여러 검증 기능을 이어서 선언할 수 있다.

 

.andExpect(status().isOK())

 - mvc.perfom의 결과를 검증한다.

 - HTTP Header의 Status를 검증한다.

 - 우리가 흔히 알고 있는 200, 400, 500 등의 상태를 검증한다.

 - 여기선 OK 즉, 200인지 아닌지를 검증한다.

 

.andExpect(content().string( ... ))

 - mvc.perform의 결과를 검증한다.

 - 응답 본문의 내용을 검증한다.

2) @AutoConfigureMockMvc 사용하기  

- @AutoConfigureMockMvc는 @WebMvcTest와 비슷하게 사용할 수 있는 어노테이션이다.

- @SpringBootTest에는 웹 애플리케이션 테스트를 지원하는 webEnvironment 속성이 있다. 이 속성을 생략하면 기본값으로 WebEnvironment.MOCK이 설정되어 있는데, 이 설정에 의해서 서블릿 컨테이너가 모킹된다.

- @SpringBootTest(webEnvironment=WebEnvironment.MOCK) 설정으로 모킹한 객체를 의존성 주입받으려면 @AutoCOnfigureMockMvc를 클래스 위에 추가 해야한다.

 

@AutoConfigureMockMvc

- @WebMvcTest와 가장 큰 차이점은은 컨트롤러뿐만 아니라 테스트 대상이 아닌 @Service@Repository붙은 객체들도 모두 메모리에 올린다는 것이다.

- 간단하게 테스트하기 위해서는 @AutoConfigureMockMvc가 아닌 @WebMvcTest를 사용해야 한다.

- @WebMvcTest는 @SpringBootTest와 같이 사용될 수 없다. 왜냐하면 각자 서로의 MockMvc를 모킹하기 때문에 충돌이 발생하기 때문이다.

 


MockMvc 메소드 이해하기

perform()

- MockMvc가 제공하는 perform()메소드를 사용하면 브라우저에서 서버에 URL 요청을 하듯 컨트롤러를 실행시킬 수 있다.

- MockMvc의 perform() 메소드는 RequestBuilder 객체를 인자로 받는데, RequestBuilder 객체는 MockMvcRequestBuilder의 정적 메소드를 이용해서 생성한다.

- MockMvcRequestBuilder의 메소드들은 GET, POST, PUT, DELETE 요청 방식과 매핑되는 get(), post(), put(), delete() 메소드를 제공한다.

  그리고 이 메소드들은 MockHttpServletRequestBuilder 객체를 리턴하는데, 이 객체에 브라우저가 HTTP 요청 프로토콜에 요청 관련 정보(파라미터, 헤더, 쿠키 등)를 설정하듯 다양한 정보들을 설정할 수 있다.

- 위 코드처럼 param() 메소드를 이용하면 '키=값'의 파라미터를 여러 개 전달할 수 있다.

- MockHttpServletRequestBuilder의 메소드는 MockHttpServletRequestBuilder 객체를 다시 리턴하기 때문에 메시지 체인을 구성하여 복잡한 요청을 설정할 수 있다.

andExpect()

- perform() 메서드를 이용하여 요청을 전송하면, 그 결과로 ResultActions 객체를 리턴하는데, ResultActions는 응답 결과를 검증할 수 있는 andExpect() 메서드를 제공한다.

- andExpect()가 요구하는 ResultMatcher 객체는 MockMvcResultMatchers에 정의된 정적메서드를 통해 생성할 수 있다.

 

 

- 컨트롤러의 동작을 테스트하기 위해서는 요청도 중요하지만 사실 컨트롤러가 어떤 결과를 전송했는지 검증하는 것이 가장 중요하다.

- 서버의 응답 결과는 MockMvcResultMatchers 객체의 메소드를 이용하여 검증할 수 있다.

 

(1) status() : 응답 상태 코드 검증

- MockMvcResultMatchers의 status() 메소드는 StatusResultMatchers 객체를 리턴하는데 이 객체를 이용하면 응답 상태 코드를 검증할 수 있다.

메소드 설명
isOK()  응답 상태 코드가 정상 처리에 해당하는 200인지 확인한다.
isNotFound()  응답 상태 코드가 404 Not Found인지 확인한다.
isMethodNotAllowed()  응답 상태 코드가 메소드 불일치에 해당하는 405인지 확인한다.
isInternalServerError()  응답 상태 코드가 예외 발생에 해당하는 500인지 확인한다.
is(int status)

 몇 번 응답 상태 코드가 설정되어 있는지 확인한다.

 예) is(200), is(404), is(405), is(500)

(2) view() : 뷰/리다이렉트 검증

- 컨트롤러가 리턴하는 뷰를 검증할 때는 view() 메소드를 사용한다.

- andExpect(view().name("hello")) 코드는 컨트롤러가 리턴한 뷰 이름이 "hello"인지 검증한다.

- 만약 요청 처리 결과가 리다이렉트 응답이라면 redirectedUrl() 메소드를 사용하면 된다.

- andExpect(redirectedUrl("/index")) 코드는 "/index" 화면으로 리다이렉트했는지를 검증한다.

 

(3) model() : 모델 정보 검증

- 컨트롤러에서 저장한 모델의 정보들을 검증하고 싶으면 MockMvcResultMatchers.model() 메소드를 사용한다.

메소드 설명
attributeExists(String name)  name에 해당하는 데이터가 Model에 포함되어있는지 검증한다.
attribute(String name, Object value)  name에 해당하는 데이터가 value 객체인지 검증한다.

 

(4) andDo() : 요청/응답 전체 메시지 확인하기

- MockMvc를 이용해서 테스트를 진행할 떄, 실제로 생성된 요청과 응답 메시지를 모두 확인해보고 싶은 경우에는 perform() 메소드가 리턴하는 ResultActions의 andDo(ResultHandler handler) 메소드를 사용하면 된다.

- MockMvcResultHandlers.print() 메소드는 ResultHandler를 구현한 ConsolePrintingResultHandler 객체를 리턴한다.

- ConsolePrintingResultHandler를 andDo() 메소드 인자로 넘겨주면 콘솔에 요청/응답과 관련된 정보를 모두 출력한다.

 


내장 Tomcat으로 테스트하기

- 테스트 케이스에서 정상적으로 서블릿 컨테이너를 구동하고 테스트 결과를 확인하고 싶으면 @SpringBootTest에서 webEnvironment 속성값을 RANDOM_PORT나 DEFINED_PORT로 변경하면 된다.

webEnvironment

- 애플리케이션이 실행될 때의 웹 환경을 설정할 수 있다.

- 기본값은 Mock 서블릿을 로드하여 구동되며 위 코드에서는 랜덤 포트값을 주어 구동시킨다.

상수 의미
MOCK

 모킹된 서블릿 컨테이너를 제공하기 때문에 내장 톰캣이 구동되지 않는다.

 @AutoConfigureMockMvc 어노테이션을 사용하여 MockMvc 객체를 주입 받아 테스트에 사용할 수 있다.

RANDOM_PORT

 랜덤한 포트로 내장 톰캣을 구동하여 서블릿 컨테이너를 초기화한다.

 정상적인 서블릿 테스트가 가능하다.

 

DEFINED_PORT  RANDOM_PORT와 동일하지만, application.properties 파일에 설정된 서버 포트를 사용한다.
NONE  서블릿 기반의 환경 자체를 구성하지 않는다.,

TestRestTemplate

- webEnvironment 속성값을 WebEnvironment.RANDOM_PORT로 지정하면 더 이상 서블릿 컨테이너를 모킹하지 않기 때문에 MockMvc 객체를 목업할 수 없다. 따라서 MockMvc 객체 대신 실제 컨트롤러를 실행해줄 TestRestTemplate 객체를 주입해서 컨트롤러를 요청해야 한다.

- TestRestTemplate 객체를 이용하면 특정 URL로 서버에 요청을 전달할 수 있으며 응답 결과도 검증할 수 있다.

- getForObject() 메소드의 첫 번째 인자로 서버에 요청할 URL을 지정했고, 두 번째로 응답 결과의 타입 클래스를 지정했다.

- assertEquals() 메소드를 이용하여 응답 결과 메시지를 확인하였다.

- assertThat()라는 테스트 검증 라이브러리의 검증 메소드를 사용하여 검증하고 싶은 대상을 메소드로 인자로 받아서 결과를 확인하였다.

- assertThat()는 메소드 체이닝이 지원되어 isEqualTo(동등 비교 메소드)와 같이 메소드를 이어서 사용할 수 있다.