[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은 별다를게 없다. 클래스패스가 아닌 영역에 xml로 설정 파일을 작성했을 경우는 file 프로토콜로 접근해야 한다는 점도 신경쓸 항목이다.
추가로 필요한 annotation은 @WebAppConfiguration이다. MockMvc는 생성하는 과정에서 WebApplicationContext 객체가 필요한데 이를 주입 받기 위해 필요한 annotation인 @WebAppConfiguration이다.
그리고 Junit에서는 위 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의 기본 동작
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 : builder 패턴을 이용해서 요청을 생성하고 실행한다.
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 메서드로 정의되어있다.
메서드 명 | 설명 |
실행 결과를 표준 출력(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 = []
'tools & libs > 단위테스트(junit, spock)' 카테고리의 다른 글
[spring test] 6. @Controller Test 3 - File upload test (0) | 2023.11.27 |
---|---|
[spring test] 5. @Controller Test 2 (0) | 2023.11.27 |
[spring test] 3. @Service Layer test (0) | 2023.11.27 |
[spring test] 2. Spring Data Jpa Test (0) | 2023.11.27 |
[spring test] 1. spring과 junit (0) | 2023.11.23 |
소중한 공감 감사합니다