테스트 - MockMvc
1. MockMvc란?
스프링 프레임워크는 3.2버전 이후로 Spring MVC를 Mocking하여 웹 애플리케이션을 테스트 하는 MockMVC를 제공하고 있다.
MockMvc는 Spring MVC Test Framework이라고도 불리며 웹 애플리케이션을 실제 서버(서블릿 컨테이너)의 실행 없이 가상 환경에서 테스트 Mock request를 통해 Spring MVC 애플리케이션의 테스트를 할 수 있다. 실제 환경을 구축하지 않아도 되어 테스트가 가벼운편이며 Spring MVC 애플리케이션의 대부분의 기능들을 테스트 할 수 있다.
WebTestClient로 MockMvc를 대체할 수 있다.
2. Setup
2.1. MockMvc객체 생성
MockMvc를 설정하는 방법은 세 가지 방법이 있다.
-
방법1. 수동으로 하나 이상의 컨트롤러를 MockMvc로 만들려는 경우 아래와 같이
standaloneSetup()
메서드를 사용하면 된다.class MyWebTests { MockMvc mockMvc; @BeforeEach void setup() { this.mockMvc = MockMvcBuilders.standaloneSetup(new AccountController()).build(); } // ... }
-
방법2. 스프링이 로드한 WebApplicationContext를 통해 MockMvc를 만들려는 경우
webAppContextSetup()
메서드를 사용하면 된다.class MyWebTests { MockMvc mockMvc; @BeforeEach void setup(WebApplicationContext wac) { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); } // ... }
둘의 차이는 standaloneSetup()
의 경우 테스트 할 컨트롤러를 수동으로 초기화하고 주입하여야 하지만 webAppContextSetup()
은 스프링이 로드한 WebApplicationContext로 쉽게 만들 수 있다는 점이다. 또한 standaloneSetup()
은 테스트 하는 용도로 특정 컨트롤러만을 사용하겠다는 점에서 단위 테스트와 유사한 느낌을 주지만 webAppContextSetup()
은 스프링 컨트롤러들과 그들의 의존성까지 모두 로드하기 때문에 통합 테스트의 느낌을 준다.
둘 중에서는 웬만하면 webAppContextSetup()
를 사용하는 것을 추천한다. 겉으로 보기에는 standaloneSetup()
가 테스트 할 컨트롤러만을 주입하여 만들기에 더 빠르다 생각될 수 있지만 스프링 테스트는 기본적으로 TestContext를 캐싱하기 때문에 webAppContextSetup()
를 사용하여도 첫 테스트에만 해당 빈들을 불러오고 그 이후부터는 캐싱된 TestContext를 사용하여 속도 측면에서도 뒤떨어지지 않는다.
- 방법3.
@AutoConfigureMockMvc
를 테스트 클래스 위에 붙여주고@Autowired
를 할 경우 MockMvc객체가 자동 주입됩니다.
@SpringBootTest
@AutoConfigureMockMvc
class MyWebTests {
@Autowired
private MockMvc mockMvc;
}
2.2. 필터 추가하기
addFilters()
메서드를 사용하면 필터 추가가 가능하다.
class MyWebTests {
MockMvc mockMvc;
@BeforeEach
void setup() {
this.mockMvc = MockMvcBuilders
.standaloneSetup(new AccountController())
.addFilters(new SessionFilters())
.build();
}
// ...
}
3. 요청 보내기
mockMvc에서 요청에 대한 설정은 모두 perform()
메서드 안에 들어가게 됩니다.
perform()메서드의 파라미터 타입을 보면 RequestBuilder
인 것을 확인할 수 있습니다. 우리는 perform 안에 빌더 패턴을 사용해 request에 대한 정보를 하나씩 채워가면 됩니다.
3.1. 요청 메서드 및 query parameter 설정하기
MockMvcRequestBuilders
클래스에서는 요청 메서드로 get(), post(), put(), delete(), petch() 등 http request들을 모두 지원하고 있습니다. 우리는 상황에 맞는 요청 값을 불러와 아래와 같이 사용하면 됩니다.
mockMvc.perform(get("stations"));
url에 query parameter가 존재하는 경우에는 파라미터를 설정하는 2가지 방법이 존재합니다.
첫번째 방법은 요청 메서드 안에 2번째 이후 파라미터로 두는 방법입니다.
mockMvc.perform(get("/stations?name={name}", "line1"));
두번째 방법은 빌터 패턴을 통해 param()메서드로 파라미터를 설정하는 방법이 있습니다.
mockMvc.perform(get("/stations").param("name", "line1"));
3.2. body값 담아주기
request에 body값이 필요하다면 content()
와 contentType()
을 통해 담아줄 수 있다.
// StationRequest request = new StationRequest("강남역");
mockMvc.perform(post("/stations")
.content(objectMapper.writeValueAsString(request))
.contentType(MediaType.APPLICATION_JSON_VALUE)
)
3.3. 요청 설정 메서드
MockMvc는 request를 설정하는 많은 매서드들을 제공하고 있다.
- param / params : 쿼리 스트링 설정
- cookie : 쿠키 설정
- requestAttr : 요청 스코프 객체 설정
- sessionAttr : 세션 스코프 객체 설정
- content : 요청 본문 설정
- contentType : 본문 타입 설정
- header / headers : 요청 헤더 설정
자세한 메서드의 사용법을 알고 싶다면 해당 메서드들을 제공해주는
MockHttpServletRequestBuilder
클래스를 살펴보기 바란다.
3.4. 사용 예시
@SpringBootTest
class StationControllerTest {
MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@BeforeEach
void setUp(WebApplicationContext context) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
}
@Test
void createStation() throws Exception {
StationRequest request = new StationRequest("강남역");
mockMvc.perform(post("/stations")
.cookie(new Cookie("쿠키 이름", "쿠키 값"))
.header("헤더 이름", "헤어 값")
.content(objectMapper.writeValueAsString(request))
.contentType(MediaType.APPLICATION_JSON_VALUE));
}
}
4. 검증하기
Response를 검증을 하기 위해서는 요청을 보낸 perform()
뒤에 빌더 패턴을 사용하여 andExpect(..)
, andExpectAll(..)
들을 붙여서 검증해야합니다. 빌더패턴을 사용하는 만큼 검증 메서드가 여러개가 붙을 수 있으며 해당 메서드들을 모두 통과해야지 검증이 통과하게 된다. 만약 하나라도 검증 실패할 경우 해당 테스트는 실패하게 된다.
-
andExpect(..)
: 하나의 값을 검증하기 위해 사용한다.mockMvc.perform(get("/accounts/1")) .andExpect(status().isOk());
-
andExpectAll(..)
: 내부에 모든 값들이 통과되어야 한다.mockMvc.perform(get("/accounts/1")).andExpectAll( status().isOk(), content().contentType("application/json;charset=UTF-8"));
4.1. 검증 메서드
MockMvc에서 제공하는 검증 메서드는 아래의 메서드들이 있다.
- status : 상태 코드 검증
- header : 응답 header 검증
- content : 응답 본문 검증
- cookie : 쿠키 상태 검증
- view : 컨트롤러가 반환한 뷰 이름 검증
- jsonPath: body의 특정 값을 검증.
- redirectedUrl(Pattern) : 리다이렉트 대상의 경로 검증
- model : 스프링 MVC 모델 상태 검증
- request : 세션 스코프, 비동기 처리, 요청 스코프 상태 검증
- forwardedUrl : 이동대상의 경로 검증
- model().atrribute("userId", "fly123")
자세한 메서드의 사용법을 알고 싶다면 해당 메서드들을 제공해주는
MockMvcResultMatchers
클래스를 살펴보기 바란다.
@SpringBootTest
class StationControllerTest {
MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@BeforeEach
void setUp(WebApplicationContext context) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
}
@Test
void createStation() throws Exception {
StationRequest request = new StationRequest("강남역");
mockMvc.perform(post("/stations")
.content(objectMapper.writeValueAsString(request))
.contentType(MediaType.APPLICATION_JSON_VALUE))
.andExpect(status().isCreated())
.andExpect(header().string("Location", "/stations/1"))
.andExpect(jsonPath("$.name").value("강남역"));
}
}
4.2. 사용 예시
@SpringBootTest
class StationControllerTest {
MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@BeforeEach
void setUp(WebApplicationContext context) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
}
@Test
void createStation() throws Exception {
StationRequest request = new StationRequest("강남역");
mockMvc.perform(post("/stations")
.content(objectMapper.writeValueAsString(request))
.contentType(MediaType.APPLICATION_JSON_VALUE))
.andExpect(status().isCreated())
.andExpect(header().string("Location", "/stations/1"))
.andExpect(jsonPath("$.name").value("강남역"));
}
}
5. 기타 기능
5.1. print()
print()
메서드는 테스트를 하며 만들어지는 MockMvcResultHandlers
에서 수행된 요청의 결과를 가져와 출력해주는 일을 한다. 해당 메서드를 사용하면 우리는 테스트에 사용된 request, response에 대한 자세한 정보들을 콘솔창에서 확인할 수 있다.
@SpringBootTest
class StationControllerTest {
MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@BeforeEach
void setUp(WebApplicationContext context){
this.mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
}
@Test
void createStation() throws Exception {
StationRequest request = new StationRequest("강남역");
mockMvc.perform(post("/stations")
.content(objectMapper.writeValueAsString(request))
.contentType(MediaType.APPLICATION_JSON_VALUE))
.andDo(print())
.andExpect(status().isCreated());
}
}
andDo(print())
를 추가한 결과 테스트를 수행하면 아래와 같이 request/response의 결과를 확인할 수 있다.