tools & libs/단위테스트(junit, spock)

[spring test] 4. @Controller Test 1

  • -
반응형

이번 포스트에서는 Spring @MVC의 Controller를 테스트하는 방법에 대해서 알아보자.

 

MockMvc 설정

 

MockMvc?

Controller를 만들고 잘 동작하는지 확인하기 위해서 매번 스프링 애플리케이션을 실행하고 브라우저를 통해서 동작을 확인하는데는 매우 손이 많이 간다. 이 상황에서 파라미터의 전달, attribute, 화면 이동 등을 검증하는 일은 쉬운 일이 아니다. 또한 웹이 제대로 동작하는지 확인하기 위해서는 브라우저나 WAS처럼 우리가 프로그래밍하지 않은 요소가 개입된다.  

따라서 Controller의 단위 테스트를 위해서는 MockMvc이라는 객체가 사용된다. MockMvc는 앞서 살펴봤던 @Mock 처럼 가짜로 테스트를 위해 브라우저나 WAS의 동작을 똑같이 처리해줄 수 있는 환경이라고 생각하면 된다. MockMvc를 이용해 브라우저에서 발생하는 요청을 가상으로 만들고 컨트롤러가 응답하는 내용을 기반으로 검증을 수행한다.

 

legacy spring에서 MockMvc 설정

legacy spring에서 MockMvc를 얻기 위해서는 3개의 annotation이 필요하다.

@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"})
@WebAppConfiguration

@ExtendWith나 @ContextConfiguration은 별다를게 없다. 주의할 점은 servlet-context.xml이나 root-context.xml이 classpath에 존재하지 않기 때문에 file 프로토콜로 접근해야 한다는 점이다.

추가로 필요한 annotation은 @WebAppConfiguration이다. MockMvc는 생성하는 과정에서 WebApplicationContext 객체가 필요한데 이를 주입 받기 위해 필요한 annotation인 @WebAppConfiguration이다.

그리고 Jupiter에서는 위 3개의 annotation을 포함하는 compose annotation으로 @SpringJunitWebConfig를 제공한다. 이 녀석을 사용하면 한방에 동일한 효과를 낼 수 있다.

@SpringJUnitWebConfig(locations = {"file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"})

이제 WebApplicationContext 객체를 얻을 수 있고 이를 이용해서 MockMvc 객체를 얻을 수 있다.

@SpringJUnitWebConfig(locations = {"file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"})
public class MVCTest {
  @Autowired
  private  WebApplicationContext webCtx;
  
  private MockMvc mockMvc;
  @BeforeEach
  public void setUp() {
    mockMvc = MockMvcBuilders.webAppContextSetup(webCtx).build();
  }
}

 

spring boot에서 MockMvc 설정

SpringBoot에서 @Controller를 slice 테스트 하기 위해서는 @WebMvcTest를 사용하는데 이는 바로 MockMvc를 주입받을 수 있다.

@Target(ElementType.TYPE)
@BootstrapWith(WebMvcTestContextBootstrapper.class)
@ExtendWith(SpringExtension.class)
@OverrideAutoConfiguration(enabled = false)
@TypeExcludeFilters(WebMvcTypeExcludeFilter.class)
@AutoConfigureCache
@AutoConfigureWebMvc
@AutoConfigureMockMvc
@ImportAutoConfiguration
public @interface WebMvcTest {
  Class<?>[] controllers() default {};
}
  • @TypeExcludeFilters(WebMvcTypeExcludeFilter.class): @Controller, @ControllerAdvice, @JsonComponent, Converter, Filter, HandlerInterceptor등 웹과  관련된 빈을 스캔해서 등록 한다. (@Service 등은 @MockBean 으로 처리)
  • @AutoConfigurerCache: 기본 CacheType으로 CacheType.NONE을 설정해서 데이터를 캐싱하지 않는다.
  • @AutoConfigureWebMvc: SpringMVC테스트에 필요한 환경을 로딩한다.
  • @AutoConfigureMockMvc: MockMvc를 자동으로 구성한다.
  • 특정 @Controller만을 테스트 대상으로 지정할 때는 controllers ( = value) 속성을 이용한다.

MockMvc를 자동으로 구성하기 때문에 legacy에 비해 테스트 코드가 훨씬 간단해 졌다.

@WebMvcTest(MainController.class)
public class MvcTestOnlyController {
  @Autowired
  MockMvc mock;
}

 

MockMvc 사용법

 

MockMvc의 기본 동작

요청(request)을 만들어서 실행(perform)하면 돌아오는 응답(response)에 대해 기대값을 검증(expect)한다.

MockMvc 객체는 크게 3가지 동작을 갖는다.

메서드 설명
MockMvc.perform(요청) MockHttpServletRequestBuilder를 통해서 생성된 요청을 가상 서버에 전달한다.
ResultActions.andExpect(검증 내용) 요청 결과 즉 응답에 대해 검증을 수행한다. 검증의 항목은 ResultMatchers를 반환하는 handler(), status(), model(), view() 등 메서드들을 활용한다.
ResultActions.andDo(처리할 내용) 콘솔 출력등 처리할 일을 ResultHandler 타입으로 작성한다.

 

요청 만들기 및 perform

요청은 MockMvcRequestBuilders의 static 메서드인 get, post, put, delete, fileUpload 등을 이용해서 MockHttpServletRequestBuilder 객체를 생성하는 것에서 시작한다. MockHttpServletRequestBuilder는 ServletRequest를 구성하기에 필요한 다양한 메서드를 제공한다.

메서드명 설명
param / params 요청 파라미터를 설정한다.
header / headers 요청 헤더를 설정한다. 
cookie 쿠키를 설정한다.
requestAttr request scope에 attribute를 설정한다.
flashAttr flash scope에 attribute를 설정한다.
sessionAttr session scope에 attribute를 설정한다.
locale Locale 정보를 설정한다.
characterEncoding 요청의 인코딩 정보를 설정한다.
contentType Enum인 MediaType으로 요청의 컨텐트 타입을 설정한다.
file fileUpload로 ServletRequestBuilder를 생성한 경우 업로드 파일을 지정한다.

위 메서드들은 method chaining을 지원하기 때문에 쭉 연결해서 작성하는 것이 일반적이다. 만들어진 요청은 MockMvc의 perform 메서드에 파라미터로 설정해준다.

// given
String url = "/add";
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("a", "10");
map.add("b", "20");
Cookie nameCookie = new Cookie("name", "홍길동");

// when
MockHttpServletRequestBuilder builder = get(url).params(map).cookie(nameCookie);
ResultActions result = mockMvc.perform(builder);

 

andExpect

perform의 결과로 ResultActions가 반환되는데 이 객체의 andExpect 메서드에 ResultMatcher를 넘겨줘서 검증한다. ResultMatcher는 아래의 MockMvcResultMatchers가 가지는 static 메서드를 통해서 얻는다.

메서드명 설명
handler 요청에 매핑된 컨트롤러를 검증한다.
header 응답 헤더의 값을 검증한다.
cookie 응답을 통해 전달된 쿠키를 검증한다.
content 응답의 본문 내용을 검증한다.
view Controller의 handler method가 반환한 view의 이름을 검증한다.
model model에 담긴 attribute 값을 검증한다.
flash flash scope에 담긴 attribute 값을 검증한다.
forwardedUrl / forwardedUrlPattern forward로 이동하는 대상의 경로를 검증한다.
redirectedUrl / redirectedUrlPattern redirect로 이동하는 경우 대상의 경로를 검증한다.
status Http 상태 코드를 이용해 검증한다.
request request scope, session scope의 attribute, 비동기 처리관련 검증

 

ResultActions 역시 method chaining 형태로 작성할 수 있다.

result.andExpect(handler().handlerType(HelloController.class))
      .andExpect(handler().methodName("add"))
      .andExpect(forwardedUrl("index"))
      .andExpect(header().stringValues("Content-Language", "en"))
      .andExpect(model().attribute("message", "4.5와 3의 합은 7.5입니다."))
      .andExpect(view().name("index"))
      .andExpect(status().is(200));

 

andDo

andDo는 andExpect와 마찬가지로 ResultActions의 메서드이다. 파라미터로 ResultHandler 를 전달하는데 이것들은 MockMvcResultHandlers에 static 메서드로 정의되어있다.

메서드 명 설명
print 실행 결과를 표준 출력(System.out)을 이용해 출력한다.
mock.perform(builder)
    .andExpect(status().is(200))
    .andDo(print());
MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /add
       Parameters = {a=[10], b=[20]}
          Headers = [Cookie:"name=홍길동"]
             Body = null
    Session Attrs = {}

Handler:
             Type = com.quietjun.junittest.controller.HomeController
           Method = com.quietjun.junittest.controller.HomeController#add(double, double, String, Model)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = showMessage
             View = null
        Attribute = result
            value = 30.0
        Attribute = name
            value = 홍길동

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Language:"en", Content-Type:"text/html;charset=UTF-8"]
     Content type = text/html;charset=UTF-8
             Body = <!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">

<title>Insert title here</title>
</head>
<body>
	<h1>30.0</h1>
</body>
</html>
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

 

반응형
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.