자바 백엔드 입문 53편. Phase 7 마무리. MockMvc로 컨트롤러를 실제 Tomcat 없이 HTTP 요청·응답을 시뮬레이션하는 표준 패턴을 실전 코드로 풀어쓴 학습 노트.
이 글은 자바 백엔드 입문 시리즈 59편 중 53편이에요. Phase 7 Testing 마지막 글입니다. 52편에서 짧게 만난 MockMvc를 본격적으로 풀어 가요. Tomcat 없이 HTTP 요청·응답을 시뮬레이션하는 핵심 도구.
MockMvc — Tomcat 없이 HTTP 테스트
@SpringBootTest 통합 테스트로 실제 Tomcat을 띄울 수도 있지만 — 너무 무거워요. MockMvc는 "진짜 HTTP 서버 없이" DispatcherServlet 흐름을 메모리에서 시뮬레이션해줘요.
@WebMvcTest(OrderController.class)
class OrderControllerTest {
@Autowired private MockMvc mockMvc;
@MockBean private OrderService orderService;
@Test
void 주문_조회_성공() throws Exception {
// Given
when(orderService.findById(1L))
.thenReturn(new Order(1L, 10000, "PENDING"));
// When + Then
mockMvc.perform(get("/api/orders/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.amount").value(10000))
.andExpect(jsonPath("$.status").value("PENDING"));
}
}
핵심 부품 — MockMvc + @WebMvcTest 조합. Controller 한 개만 띄우고 서비스는 @MockBean 으로 대체.
perform·andExpect·andDo 3단계
MockMvc 테스트의 표준 구조.
mockMvc.perform(요청) // 1단계: HTTP 요청 흉내
.andExpect(검증1) // 2단계: 응답 검증
.andExpect(검증2)
.andDo(부가 동작); // 3단계: 결과 출력·로깅
perform — 요청 빌더
// GET
mockMvc.perform(get("/api/orders/1"));
// POST + JSON 본문
mockMvc.perform(post("/api/orders")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"productId\":1,\"amount\":10000}"));
// 쿼리 파라미터
mockMvc.perform(get("/api/orders")
.param("status", "ACTIVE")
.param("page", "1"));
// 헤더
mockMvc.perform(get("/api/orders/1")
.header("Authorization", "Bearer token123"));
andExpect — 응답 검증
자주 만나는 검증 함수.
| 함수 | 용도 |
|---|---|
status().isOk() |
200 |
status().isCreated() |
201 |
status().isBadRequest() |
400 |
status().isNotFound() |
404 |
jsonPath("$.field").value(...) |
JSON 필드 값 |
jsonPath("$.list").isArray() |
배열 여부 |
jsonPath("$.list.length()").value(3) |
배열 크기 |
content().string("...") |
응답 본문 전체 |
header().string("Location", "/orders/1") |
응답 헤더 |
andDo — 부가 동작
.andDo(print()) // 요청·응답 콘솔 출력 (디버깅)
.andDo(log()) // 로거에 출력
테스트 실패 시 "실제로 어떤 응답이 왔는지" 보기 위해 print() 자주 박아요.
ObjectMapper로 JSON 본문 깔끔하게
문자열로 JSON 박으면 따옴표 escape가 번거로워요. Jackson ObjectMapper 활용.
@Autowired private ObjectMapper objectMapper;
@Test
void 주문_생성() throws Exception {
OrderRequest request = new OrderRequest(1L, 10000);
mockMvc.perform(post("/api/orders")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isCreated());
}
ObjectMapper도 @WebMvcTest 자동 주입. 별도 설정 X.
@Valid 검증 실패 테스트
33편 의 글로벌 예외 처리 + 34편 의 Bean Validation이 합쳐진 검증 테스트.
@Test
void 잘못된_요청_검증_실패() throws Exception {
String invalidJson = "{\"productId\":null,\"amount\":-100}";
mockMvc.perform(post("/api/orders")
.contentType(MediaType.APPLICATION_JSON)
.content(invalidJson))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.code").value("VALIDATION_FAILED"))
.andExpect(jsonPath("$.fieldErrors[*].field")
.value(containsInAnyOrder("productId", "amount")));
}
검증 실패 시 글로벌 핸들러가 400 + 오류 JSON을 만들어줘요. 그 응답 구조를 그대로 검증.
인증 테스트 — @WithMockUser
Spring Security가 박혀 있을 때, 인증된 사용자 시뮬레이션.
@Test
@WithMockUser(username = "alice", roles = {"USER"})
void 인증된_사용자_조회() throws Exception {
mockMvc.perform(get("/api/orders/me"))
.andExpect(status().isOk());
}
@Test
@WithMockUser(roles = {"USER"})
void 권한_없으면_403() throws Exception {
mockMvc.perform(post("/api/admin/cleanup"))
.andExpect(status().isForbidden());
}
@WithMockUser 가 "이 테스트는 alice 사용자로 인증된 상태" 를 자동 설정. Spring Security 통합 테스트 표준 패턴.
MockMvcTester — 새로운 AssertJ 통합
Spring Framework 6.2부터 새 빌더 MockMvcTester 등장. AssertJ 스타일.
@Autowired private MockMvcTester mockMvcTester;
@Test
void 주문_조회() {
assertThat(mockMvcTester.get().uri("/api/orders/1"))
.hasStatusOk()
.bodyJson()
.extractingPath("$.amount").isEqualTo(10000);
}
기존 MockMvc보다 더 자연스러운 체이닝. 신규 프로젝트에서 시도해볼 만하지만 — 기존 MockMvc도 여전히 표준.
@SpringBootTest + MockMvc 통합 테스트
전체 흐름을 실제로 검증하려면.
@SpringBootTest
@AutoConfigureMockMvc
@Transactional
class OrderE2ETest {
@Autowired private MockMvc mockMvc;
@Autowired private OrderRepository orderRepo;
@Autowired private ObjectMapper objectMapper;
@Test
void 주문_생성_API_전체_흐름() throws Exception {
OrderRequest req = new OrderRequest(1L, 10000);
mockMvc.perform(post("/api/orders")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(req)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").exists());
// DB 실제 반영 확인
assertThat(orderRepo.findAll()).hasSize(1);
}
}
@SpringBootTest + @AutoConfigureMockMvc 가 "전체 컨테이너 띄움 + MockMvc 활성화". 실제 Service·Repository·DB까지 다 거쳐 검증.
건강한 테스트 구성 = 단위 테스트 80%(가장 가벼움, Mockito만) + 슬라이스 테스트 15%(@WebMvcTest·@DataJpaTest) + E2E 통합 테스트 5%(@SpringBootTest). 위로 갈수록 무겁고 느림 — 적절히 분배하는 게 핵심.
한 줄 정리 — MockMvc = Tomcat 없이 HTTP 요청 시뮬레이션. perform·andExpect·andDo 3단계 구조. @WebMvcTest 와 조합으로 가볍게, @SpringBootTest + @AutoConfigureMockMvc 로 전체 흐름 검증.
시험 직전 한 번 더 — MockMvc 입문자가 매번 헷갈리는 것
- MockMvc = Tomcat 없이 HTTP 요청·응답 시뮬레이션
- 표준 구조 =
perform→andExpect→andDo - 요청 빌더 =
get·post·put·patch·delete - 본문 =
.contentType(MediaType.APPLICATION_JSON).content("...") - ObjectMapper로 JSON 직렬화 자동
- 검증 =
status().isOk()·jsonPath("$.field") jsonPath= JSON 경로 표현식 ($= 루트)andDo(print())= 디버깅용 응답 출력@WebMvcTest(컨트롤러.class)= Controller만 띄움 (빠름)@MockBean= Service·Repository 가짜 객체로@WithMockUser= Spring Security 인증 시뮬레이션@SpringBootTest + @AutoConfigureMockMvc= 전체 통합 + MockMvc- 검증 실패 응답 =
status().isBadRequest()+jsonPath("$.fieldErrors") MockMvcTester= Spring Framework 6.2 신규 AssertJ 스타일- 기존 MockMvc도 여전히 표준
- 테스트 피라미드 = 단위 80% + 슬라이스 15% + E2E 5%
- 가벼운 테스트가 빠르고 안정 — 무거운 테스트는 적게
@Transactional+ 자동 롤백으로 DB 격리- MockMvc는 정적 import 표준 (
MockMvcRequestBuilders.*·MockMvcResultMatchers.*) - IntelliJ에서 자동 import 처리됨
시리즈 다른 편 (앞뒤 글 모음)
이전 글:
- 48편 — JPA Auditing @CreatedDate @LastModifiedDate
- 49편 — JPA 메서드 이름 쿼리 @Query
- 50편 — QueryDSL 타입 안전 동적 쿼리
- 51편 — 영속성 컨텍스트와 LazyLoading
- 52편 — @SpringBootTest 통합 테스트
다음 글: