Spring mvc test framework에서는 API를 통한 Spring MVC 테스트를 지원한다. MVC Test Framework는 내부에서 TestContext를 통해 실제 스프링 구성을 로드하고, 실행 ServletContext를 사용없이 MVC 테스트가 가능하다.
기존 Spring 3.2버전까지 스프링 MVC Controller를 테스트하는 방법은 Controller를 객체화하거나 객체주입하고 Mock객체(MockHttpServletRequest, MockHttpServletResponse)를 사용하여 단위테스트를 작성하는 것이었다.
그러나 이러한 테스트 방식은 Controller내부에서 쓰이는 많은 annotation기능과 request처리과정의 로직들을 모두 검증/지원하지는 못한다는 단점이 있다. (@initBinder, @ModelAttribute, @ExceptionHandler 등…)
Spring 3.2부터는 Spring mvc test framework에서 Spring MVC Test를 보다 쉽고 간편하게 할 수 있는 방법을 제시한다.
Spring MVC Test는 “mock”구현을 기반으로하며 서블릿 컨테이너 실행 없이 동작하므로 JSP렌더링을 제외한 Request, Response처리가 동작한다. 물론 forward/redirect가 실제 동작하는 것이 아니며 “forward”또는 “redirect”로 호출된 URL이 저장되며 test코드 내부에서 예상값을 확인해볼 수 있다.
Spring MVC Test에서는 JSP를 포함한 Freemarker, Velocity, Thymeleaf와 같은 뷰 타입도 지원하며 HTML, JSON, XML타입의 렌더링 등 다양한 처리방식을 지원하고 있다.
Spring MVC Test에 대해 살펴보기에 앞서 테스트 코드 예를 살펴보자.
다음은 JSON요청 사용 예이다.
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import static org.springframework.test.web.servlet.MockMvcBuilder.*; @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @ContextConfiguration("test-servlet-context.xml") public class ExampleTests { @Autowired private WebApplicationContext wac; private MockMvc mockMvc; @Before public void setup() { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); } @Test public void getAccount() throws Exception { this.mockMvc.perform(get("/accounts/1").accept(MediaType.parseMediaType("application/json;charset=UTF-8"))) .andExpect(status().isOk()) .andExpect(content().contentType("application/json")) .andExpect(jsonPath("$.name").value("Lee")); } }
위의 코드에서 MockMvc의 perform함수를 통해 ”/accounts/1” url로 request를 수행하고 응답받은 response를 통해 상태값(status:200), 컨텐트 타입(“application/json”), JSON으로 받은 값들을 확인할 수 있다.
MVC Test의 TestContext는 WebApplicationcontext로 동작한다.
Test이전의 setup에서는 Spring mvc test에 필요한 MockMvc의 객체를 가져와야한다. setup option에는 다음 두가지 방법이 있다.
@ContextConfiguration의 xml에서 설정을 읽어들이고 WebApplicationContext를 주입하여 mockMvc를 생성한다.
@RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @ContextConfiguration("my-servlet-context.xml") public class MyWebTests { @Autowired private WebApplicationContext wac; private MockMvc mockMvc; @Before public void setup() { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); } // ... }
Controller가 생성되면서 기본 Spring mvc가 구성된다.
public class MyWebTests { private MockMvc mockMvc; @Before public void setup() { this.mockMvc = MockMvcBuilders.standaloneSetup(new AccountController()).build(); } // ... }
Web MVC Layer의 구성에 따라 bean들이 호출되기 위해서는 설정 정보가 필요하기 때문에 첫번째 setUp방식을 권장한다.
테스트코드 사용 시, 필요한 API는 Static Import를 선언하여 편리하게 바로 호출해주는 것이 좋다.
예를 들어 MockMvcRequestBuilders.*, MockMvcResultMatchers.*클래스 등과 같이 많이 쓰는 것들은 Static Import로 선언해주도록 한다.
import static org.springframework.test.web.server.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;
다음은 Spring mvc 테스트코드에서 사용 가능한 MockMvc 함수이다.
함수명 | 설명 | 예 |
---|---|---|
perform | 해당 경로로 요청하며 이때 호출할 URL과 HTTP METHOD를 설정할 수 있다 | .perform(get(”/account/1”) |
param | 파라미터를 설정한다. | .param(“key”, “value”) |
cookie | 쿠키를 설정한다. | .cookie(new Cookie(“key”, “value”) |
sessionAttr | 세션을 설정한다. | sessionAttr(“key”, “value”) |
accept | response를 받을 Accept값을 설정한다. | .accept(MediaType.parseMediaType(“application/json;charset=UTF-8”))) |
andExpect | 예상값의 Assert함수 | andExpect(status().isOk() |
andDo | 요청/응답에 대한 처리를 한다. | andDo(print()) |
andReturn | 리턴 처리한다. | .andReturn() |
request요청처리를 위해 MockMvc의 perform함수를 사용하며 내부에서 MockHttpServletRequest의 값을 설정하여 request요청을 할 수 있다.
mockMvc.perform(post("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON));
HTTP메소드 외에 fileUpload메소드를 통해 내부에서 MockMultipartHttpServletRequest의 객체를 만들어 업로드요청을 수행할 수 있다.
mockMvc.perform(fileUpload("/doc").file("a1", "ABC".getBytes("UTF-8")));
URI template에서 Query String 파라미터를 지정할 수도 있다.
mockMvc.perform(get("/hotels?foo={foo}", "bar"));
또한 request파라미터를 추가할 수도 있다.
mockMvc.perform(get("/hotels").param("foo", "bar"));
요청 URI에서 contextPath와 servletPath는 생략하는 것이 바람직하지만 요청시 Full URI와 함께 테스트해야하는 경우, request매핑이 제대로 작동하도록 contextPath와 servletPath를 설정해주도록 한다.
mockMvc.perform(get("/app/main/hotels/{id}").contextPath("/app").servletPath("/main"))
매번 request요청 시 contextPath와 servletPath를 설정하는 것이 번거스러우므로, setup시에 미리 설정하는 것이 편리하다.
public class MyWebTests { private MockMvc mockMvc; @Before public void setup() { mockMvc = standaloneSetup(new AccountController()) .defaultRequest(get("/") .contextPath("/app").servletPath("/main") .accept(MediaType.APPLICATION_JSON).build(); } }
예상 결과값을 위해 andExpect함수를 사용하며 하나 이상 사용 가능하다.
MockMvcResultMatchers.* 를 static import로 정의하여 andExpect함수 내에서 제공함수를 사용할 수 있다.
MockMvcResultMatchers의 제공함수는 다음과 같이 두가지 종류가 있다.
response상태 확인 시,
mockMvc.perform(get("/accounts/1")).andExpect(status().isOk());
andExpect함수를 여러개 사용 가능 시,
mockMvc.perform(post("/persons")) .andExpect(status().isOk()) .andExpect(model().attributeHasErrors("person"));
request요청 결과를 출력할 수도 있다. print메소드를 통해 요청 처리시 관련된 모든 결과 데이터를 출력해준다.
mockMvc.perform(post("/persons")) .andDo(print()) .andExpect(status().isOk()) .andExpect(model().attributeHasErrors("person"));
andReturn메소드를 통해 return결과를 반환할 수도 있다.
MvcResult mvcResult = mockMvc.perform(post("/persons")).andExpect(status().isOk()).andReturn();
같은 예상값을 테스트할 때는 setUp시에 다음과 같이 설정한다.
standaloneSetup(new SimpleController()) .alwaysExpect(status().isOk()) .alwaysExpect(content().contentType("application/json;charset=UTF-8")) .build()
Filter인스턴스를 두개 이상 등록하고자 할 때, mockMvc 세팅시에 다음과 같이 필터를 추가할 수 있다.
mockMvc = standaloneSetup(new PersonController()).addFilters(new CharacterEncodingFilter()).build();