티스토리 뷰
안녕하세요!
오늘은 Spring MVC 컨트롤러를 테스트할 때 자주 사용되는 두 도구, MockMvc와 MockMvcTester를 비교해보겠습니다.
Spring Framework는 웹 애플리케이션 테스트를 위해 다양한 도구를 제공하는데, 이 중 MockMvc는 오랜 기간 표준으로 자리 잡았고, 최근 Spring 6.2부터 소개된 MockMvcTester는 더 현대적이고 fluent한 API를 제공합니다. 이 포스팅에서는 먼저 각 개념을 설명한 후, 비교를 통해 차이점을 분석하고, 마지막으로 실제 예시 코드를 통해 어떻게 사용하는지 보여드리겠습니다.
이 비교는 Spring Boot를 기반으로 한 테스트 환경을 가정하며, 실제 프로젝트에서 컨트롤러 테스트를 효율적으로 적용하는 데 도움이 될 것입니다. 그럼 시작해볼까요?
1. 개념 설명
1.1 MockMvc란?
MockMvc는 Spring Framework의 Test 모듈에서 제공하는 핵심 클래스입니다. 주로 MVC 컨트롤러를 테스트하기 위해 설계되었으며, 실제 서버를 구동하지 않고도 HTTP 요청을 시뮬레이션할 수 있게 해줍니다. 이는 단위 테스트나 통합 테스트에서 매우 유용합니다. MockMvc를 사용하면 DispatcherServlet을 통해 요청을 처리하고, 응답을 검증할 수 있습니다.
MockMvc의 주요 특징은 다음과 같습니다:
- 모킹 기반 테스트: 실제 데이터베이스나 외부 서비스를 모킹(mock)하여 컨트롤러 레이어만 집중적으로 테스트할 수 있습니다.
- 빌더 패턴 사용:
MockMvcBuilders를 통해 설정하며,@WebMvcTest어노테이션과 함께 사용하면 Spring Boot에서 자동으로 MockMvc 인스턴스를 주입받을 수 있습니다. - HTTP 메서드 지원: GET, POST, PUT, DELETE 등 다양한 HTTP 메서드를 지원하며, 헤더, 쿼리 파라미터, 바디 등을 자유롭게 설정할 수 있습니다.
- 검증 기능: 응답 상태 코드, 헤더, 바디 내용을 검증할 수 있지만, 기본적으로 JUnit의 assertions를 사용합니다.
예를 들어, Spring Boot 프로젝트에서 @WebMvcTest를 사용하면 전체 애플리케이션 컨텍스트를 로드하지 않고 MVC 슬라이스만 로드하여 빠른 테스트가 가능합니다. 이는 테스트 속도를 높이고, 불필요한 빈(Bean) 로드를 피하는 데 효과적입니다. MockMvc는 Spring 3.x부터 존재해온 안정적인 도구로, 많은 레거시 프로젝트에서 여전히 사용되고 있습니다.
그러나 MockMvc의 API는 다소 verbose(장황)할 수 있어, 코드가 길어지기 쉽습니다. 예를 들어, 요청 빌드와 응답 검증이 여러 줄에 걸쳐 작성되곤 합니다.
1.2 MockMvcTester란?
MockMvcTester는 Spring Framework 6.2에서 새롭게 소개된 API로, 기존 MockMvc를 기반으로 하지만 더 fluent하고 표현력 있는 테스트를 작성할 수 있도록 설계되었습니다. 이는 AssertJ 라이브러리와 긴밀하게 통합되어 assertions를 더 자연스럽게 사용할 수 있게 합니다. MockMvcTester의 목적은 개발자들이 더 읽기 쉽고 유지보수하기 쉬운 테스트 코드를 작성하도록 돕는 것입니다.
MockMvcTester의 주요 특징은 다음과 같습니다:
- Fluent API: 체인 메서드를 통해 요청과 검증을 한 줄로 연결할 수 있습니다. 이는 코드의 가독성을 크게 향상시킵니다.
- AssertJ 통합: 기본 assertions 대신 AssertJ의 fluent assertions를 사용해, 더 세밀하고 표현력 있는 검증이 가능합니다. 예를 들어, JSON 응답을 쉽게 파싱하고 검증할 수 있습니다.
- MockMvc 기반: 내부적으로 MockMvc를 사용하므로, 기존 MockMvc 테스트를 쉽게 마이그레이션할 수 있습니다.
MockMvcTester.from(mockMvc)처럼 기존 MockMvc 인스턴스를 래핑합니다. - 추가 assertions: HTTP 응답의 JSON, XML, 또는 텍스트를 직접적으로 assert할 수 있는 메서드를 제공합니다. 예를 들어,
body().jsonPath().hasJsonPathValue()같은 메서드가 내장되어 있습니다. - Spring Boot 호환: Spring Boot 3.2 이상에서 잘 동작하며,
@WebMvcTest와 함께 사용 가능합니다.
MockMvcTester는 Petri Kainulainen 같은 개발자 커뮤니티에서 "더 깨끗한 테스트 작성"을 강조하며 소개되었습니다. JetBrains의 가이드에서도 MockMvcTester를 사용하면 custom assertions를 통해 결과를 더 표현력 있게 검증할 수 있다고 언급합니다. 이는 특히 복잡한 API 응답을 다룰 때 유용합니다. 그러나 Spring 6.2 이전 버전에서는 사용할 수 없으므로, 프로젝트의 Spring 버전에 따라 선택해야 합니다.
요약하자면, MockMvc는 전통적이고 안정적인 기반 도구이며, MockMvcTester는 그 위에 현대적인 레이어를 추가한 업그레이드 버전이라고 볼 수 있습니다.
2. 비교 분석
이제 MockMvc와 MockMvcTester를 여러 측면에서 비교해보겠습니다. 비교를 위해 테이블 형식을 사용하겠습니다. 이는 각 도구의 강점과 약점을 명확히 보여줄 것입니다.
2.1 주요 차이점 테이블
| 항목 | MockMvc | MockMvcTester |
|---|---|---|
| 소개 시기 | Spring 3.x부터 (오랜 역사) | Spring 6.2부터 (최근 도입) |
| API 스타일 | 빌더 패턴 기반, verbose (장황한 코드) | Fluent API, 체인 메서드 (간결하고 읽기 쉬움) |
| Assertions | JUnit이나 Hamcrest 같은 기본 assertions 사용 | AssertJ 통합으로 더 표현력 있는 assertions (e.g., JSON 검증 쉬움) |
| 가독성 | 코드가 여러 줄로 나뉘어 다소 복잡함 | 한 줄 체인으로 작성 가능, 유지보수 용이 |
| 호환성 | 모든 Spring 버전에서 사용 가능 | Spring 6.2 이상 필요 |
| 성능 | 가볍고 빠름 (MVC 슬라이스 테스트) | MockMvc 기반이므로 비슷하지만, 추가 레이어로 약간 오버헤드 있을 수 있음 |
| 커스텀 확장 | 직접 matcher나 helper 메서드 작성 필요 | 내장 custom assertions 제공 (e.g., body assertions) |
| 마이그레이션 | - | 기존 MockMvc 테스트를 쉽게 변환 가능 (from(mockMvc) 메서드) |
2.2 장단점 비교
- MockMvc의 장점:
- 안정성과 호환성: 오래된 도구라서 문서와 예제가 풍부합니다. Spring Boot 1.x부터 사용 가능하므로 레거시 프로젝트에 적합합니다.
- 가벼움: 추가 의존성 없이 기본 Spring Test로 충분합니다.
- 유연성: 저수준 API라서 세밀한 커스터마이징이 가능합니다. 예를 들어, custom filter나 interceptor를 쉽게 추가할 수 있습니다.
- MockMvc의 단점:
- 코드 길이: 요청 빌드(
perform()), 응답 검증(andExpect())이 반복되어 코드가 길어집니다. 가독성이 떨어질 수 있습니다. - Assertions 한계: 기본 matcher가 제한적이라, 복잡한 JSON 검증 시 JsonPath나 추가 라이브러리가 필요합니다.
- 코드 길이: 요청 빌드(
- MockMvcTester의 장점:
- 가독성과 표현력: Fluent API 덕분에 테스트 코드가 자연어처럼 읽힙니다. AssertJ 통합으로 "isEqualTo" 대신 "hasSize", "containsExactly" 같은 메서드를 사용해 더 직관적입니다.
- 생산성 향상: custom assertions가 내장되어 있어, boilerplate 코드가 줄어듭니다. 예를 들어, 응답 바디를 직접 JSON으로 파싱하고 검증할 수 있습니다.
- 현대적 접근: 최근 트렌드에 맞춰 테스트 코드를 더 declarative하게 작성할 수 있습니다.
- MockMvcTester의 단점:
- 버전 의존성: Spring 6.2 이상이 필요하므로, 오래된 프로젝트에서는 업그레이드가 필요합니다.
- 학습 곡선: 기존 MockMvc 사용자라면 새로운 API를 익혀야 합니다. 하지만 마이그레이션이 쉽다는 점이 보완됩니다.
- 오버헤드: AssertJ 의존성을 추가해야 할 수 있으며, 매우 간단한 테스트에서는 과도할 수 있습니다.
2.3 언제 어떤 걸 사용하나?
- MockMvc 추천 시나리오: 간단한 테스트, 레거시 프로젝트, 또는 저수준 제어가 필요할 때. @SpringBootTest와 함께 전체 컨텍스트를 테스트할 때도 적합합니다.
- MockMvcTester 추천 시나리오: 새로운 프로젝트, 복잡한 API 검증, 또는 코드 가독성을 우선할 때. 특히 팀에서 AssertJ를 이미 사용 중이라면 자연스럽게 채택할 수 있습니다.
전반적으로 MockMvcTester는 MockMvc의 진화된 형태로, 미래 지향적 선택입니다. 하지만 프로젝트 상황에 따라 선택하세요.
3. 예시 코드
이제 실제 코드를 통해 어떻게 사용하는지 보겠습니다. 간단한 REST 컨트롤러를 가정합니다. 컨트롤러는 /users 엔드포인트로 사용자 목록을 반환하는 GET 메서드를 가집니다.
3.1 테스트 대상 컨트롤러
먼저, 예시 컨트롤러 코드입니다:
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public List<User> getAllUsers() {
return userService.findAll();
}
}
UserService는 모킹할 서비스입니다.
3.2 MockMvc를 사용한 예시 테스트
MockMvc를 사용한 테스트 코드는 다음과 같습니다. @WebMvcTest를 사용해 MVC 슬라이스만 로드합니다.
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import java.util.Arrays;
import java.util.List;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest(UserController.class)
class UserControllerMockMvcTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
void getAllUsers_ShouldReturnUserList() throws Exception {
// Given
List<User> users = Arrays.asList(new User(1L, "John"), new User(2L, "Jane"));
when(userService.findAll()).thenReturn(users);
// When & Then
mockMvc.perform(MockMvcRequestBuilders.get("/users")
.accept("application/json"))
.andExpect(status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(2))
.andExpect(MockMvcResultMatchers.jsonPath("$[0].name").value("John"))
.andExpect(MockMvcResultMatchers.jsonPath("$[1].name").value("Jane"));
}
}
이 코드에서 보듯, perform()과 andExpect()를 반복 사용합니다. JsonPath를 통해 응답을 검증하지만, 코드가 여러 줄입니다.
3.3 MockMvcTester를 사용한 예시 테스트
MockMvcTester를 사용하면 같은 테스트를 더 fluent하게 작성할 수 있습니다. AssertJ를 의존성에 추가해야 합니다.
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MockMvcTester;
import java.util.Arrays;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@WebMvcTest(UserController.class)
class UserControllerMockMvcTesterTest {
private final MockMvcTester tester;
@MockBean
private UserService userService;
public UserControllerMockMvcTesterTest(@Autowired MockMvc mockMvc) {
this.tester = MockMvcTester.from(mockMvc);
}
@Test
void getAllUsers_ShouldReturnUserList() {
// Given
List<User> users = Arrays.asList(new User(1L, "John"), new User(2L, "Jane"));
when(userService.findAll()).thenReturn(users);
// When & Then
tester.perform(get("/users").accept("application/json"))
.expectStatus().isOk()
.expectBody()
.jsonPath("$.length()").isEqualTo(2)
.jsonPath("$[0].name").isEqualTo("John")
.jsonPath("$[1].name").isEqualTo("Jane")
.andExpect(result -> assertThat(result.getResponse().getContentAsString()).contains("John"));
}
}
여기서 tester.perform() 후 체인으로 expectStatus(), expectBody()를 연결합니다. AssertJ의 isEqualTo()가 더 자연스럽습니다. 추가로 custom assert를 쉽게 추가할 수 있습니다.
3.4 추가 예시: POST 요청 테스트
MockMvc로 POST 테스트:
@Test
void createUser_ShouldReturnCreatedUser() throws Exception {
// Given
User newUser = new User(3L, "Alice");
when(userService.save(any(User.class))).thenReturn(newUser);
// When & Then
mockMvc.perform(MockMvcRequestBuilders.post("/users")
.contentType("application/json")
.content("{\"name\":\"Alice\"}"))
.andExpect(status().isCreated())
.andExpect(MockMvcResultMatchers.jsonPath("$.name").value("Alice"));
}
MockMvcTester로 POST 테스트:
@Test
void createUser_ShouldReturnCreatedUser() {
// Given
User newUser = new User(3L, "Alice");
when(userService.save(any(User.class))).thenReturn(newUser);
// When & Then
tester.perform(post("/users")
.contentType("application/json")
.content("{\"name\":\"Alice\"}"))
.expectStatus().isCreated()
.expectBody()
.jsonPath("$.name").isEqualTo("Alice")
.andExpect(result -> assertThat(result.getResponse().getStatus()).isEqualTo(201));
}
MockMvcTester 쪽이 더 간결합니다.
결론
MockMvc와 MockMvcTester는 둘 다 훌륭한 도구지만, MockMvcTester가 더 현대적이고 개발자 친화적입니다. 프로젝트에 따라 선택하세요. 만약 Spring 6.2로 업그레이드할 수 있다면 MockMvcTester를 추천합니다.
'spring' 카테고리의 다른 글
| Cloud CDN 연동 시 CORS 문제 해결: Fetch API와 이미지 태그 요청의 차이점 (0) | 2025.09.11 |
|---|---|
| [Spring Boot] CI/CD 환경에서 Firebase 인증 오류 해결기 (GcpFirestoreAutoConfiguration) (0) | 2025.09.09 |
| Spring Boot 스케줄러 잡 중복 실행 방지 구현기 (0) | 2025.09.02 |
| 🌱 Spring AI vs 🔗 LangChain4j: Java AI 프레임워크 전격 비교 (0) | 2025.08.27 |
| Could not find org.ysb33r.gradle:grolifant (0) | 2022.11.09 |
- Total
- Today
- Yesterday
- Spring Boot
- GcpFirestoreAutoConfiguration
- spring test
- springboot
- Could not find org.ysb33r.gradle:grolifant:0.16.1
- fetchapi
- ContentCachingRequestWrapper caching error
- springai
- 톰캣 로그파일 자동삭제
- Controller Testing
- teefilter file upload error
- asciidoctor sourceDir
- RAG
- 멀티 인스턴스
- asciidoctorExtensions
- Firebase 의존성 오류
- pessimistic lock
- MockMvcTester
- CloudCDN
- CloudStorage
- mysql dump sql import
- LangChain4j
- sql import
- ContentCachingRequestWrapper caching # ContentCachingRequestWrapper file upload
- Job Lock
- org.asciidoctor.jvm.convert
- url구조
- PreflightRequest
- Spring MockMvc
- 톰캣 로그파일 자동 삭제
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | ||||||
| 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 9 | 10 | 11 | 12 | 13 | 14 | 15 |
| 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 23 | 24 | 25 | 26 | 27 | 28 | 29 |
| 30 |