지금까지 Repository와 Service 계층을 통합 테스트하며 아래에서 위로 올라왔다. 드디어 외부 세계의 요청을 가장 먼저 마주하는 Presentation Layer(Controller) 에 도착했다.
Controller를 테스트할 때는 이전과 다른 전략을 사용한다. 하위 계층(Service)은 이미 충분히 테스트했고 잘 동작한다고 가정하고, Controller의 책임에만 집중하는 것이다. 이를 위해 Mocking을 사용한다.
Controller 테스트와 Mocking
Mock은 말 그대로 '가짜' 객체다. Controller가 의존하는 Service를 가짜 객체로 대체하면, 우리는 Service의 실제 로직과 상관없이 Controller의 동작(요청 매핑, 파라미터 검증 등)만을 순수하게 테스트할 수 있다.
스프링 부트에서는 이를 위해 MockMvc
와 @WebMvcTest
를 제공한다.
@WebMvcTest(controllers = ...)
: Controller 관련 빈만 로드하는 가벼운 테스트 애노테이션.@MockBean
또는@MockitoBean
: 진짜 Service 대신 컨테이너에 등록될 가짜(Mock) Service 객체.MockMvc
: 가짜 HTTP 요청을 만들어 컨트롤러를 테스트할 수 있게 해주는 도구.
요구사항: 관리자 페이지에서 신규 상품을 등록한다. (이름, 타입, 가격 등)
// ProductControllerTest.java
@WebMvcTest(controllers = ProductController.class)
class ProductControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper; // 요청 Body를 만들기 위한 직렬화 도구
@MockitoBean // ProductService를 가짜 객체로 만든다.
private ProductService productService;
@DisplayName("신규 상품을 등록한다.")
@Test
void createProduct() throws Exception {
// given
ProductCreateRequest request = ProductCreateRequest.builder()
.type(HANDMADE)
.sellingStatus(SELLING)
.name("아메리카노")
.price(4000)
.build();
// when & then
mockMvc.perform(
post("/api/v1/products/new")
.content(objectMapper.writeValueAsString(request)) // 요청 Body
.contentType(MediaType.APPLICATION_JSON)
)
.andDo(print()) // 요청/응답 전체 내용 확인
.andExpect(status().isOk()); // HTTP 상태 검증
}
}
Controller의 진짜 책임: 유효성 검증(Validation)
Controller의 가장 중요한 역할 중 하나는 외부에서 들어온 파라미터가 유효한지 검사하는 것이다. Spring Validation을 사용해 이 부분을 테스트해보자.
// ProductCreateRequest.java
public class ProductCreateRequest {
@NotNull(message = "상품 타입은 필수입니다.")
private ProductType type;
@NotBlank(message = "상품 이름은 필수입니다.")
private String name;
@Positive(message = "상품 가격은 양수여야 합니다.")
private int price;
// ...
}
이제 이 검증 로직이 잘 동작하는지 테스트한다.
// ProductControllerTest.java
@DisplayName("신규 상품을 등록할 때 상품 가격은 양수여야 한다.")
@Test
void createProductWithZeroPrice() throws Exception {
// given
ProductCreateRequest request = ProductCreateRequest.builder()
.type(HANDMADE)
.sellingStatus(SELLING)
.name("아메리카노")
.price(0) // 잘못된 값
.build();
// when & then
mockMvc.perform(
post("/api/v1/products/new")
.content(objectMapper.writeValueAsString(request))
.contentType(MediaType.APPLICATION_JSON)
)
.andDo(print())
.andExpect(status().isBadRequest()) // 400 에러를 기대
.andExpect(jsonPath("$.code").value("400")) // 응답 Body의 필드 값 검증
.andExpect(jsonPath("$.message").value("상품 가격은 양수여야 합니다."));
}
이렇게 MockMvc
를 사용하면 Service의 복잡한 로직과 상관없이 Controller의 책임인 '요청을 받고, 검증하고, 올바른 응답을 내려주는지'에만 집중해서 테스트할 수 있다.
'Software Development > Test' 카테고리의 다른 글
[Test] Mock(with Mockito) (0) | 2025.07.05 |
---|---|
[Test] Business Layer Test(with Spring Boot) (0) | 2025.07.05 |
[Test] Persistence Layer Test(with Spring Boot, JPA) (0) | 2025.07.05 |
[Test] 테스트는 '문서'다. (0) | 2025.07.05 |
[Test] TDD(Test Driven Development) (0) | 2025.07.05 |