
@SpringBootTest가 아닌 @WebMvcTest를 주로 사용합니다.
이 둘의 차이를 아는 것이 중요합니다.@WebMvcTest를 사용하겠습니다.MockMvc라는 도구를 사용하여 마치 실제 사용자가 요청을 보낸 것처럼 흉내를 낼 것입니다.package com.example.tdd_study.product;
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.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
@WebMvcTest(ProductController.class) // 테스트할 컨트롤러만 명시
class ProductControllerTest {
@Autowired
private MockMvc mockMvc; // 브라우저 역할을 하는 가짜 객체
@MockBean // 가짜 Service 빈 등록
private ProductService productService;
@Test
void 상품_등록_API() throws Exception {
// given: 요청 데이터 준비 (JSON 형태)
String requestBody = "{\"name\": \"아메리카노\", \"price\": 4000}";
// when & then: POST 요청을 보내고 검증
mockMvc.perform(MockMvcRequestBuilders.post("/products")
.contentType(MediaType.APPLICATION_JSON)
.content(requestBody))
.andExpect(MockMvcResultMatchers.status().isOk());
}
}ProductController가 없다는 에러가 발생할 것입니다.
이제 이 빨간 불을 끄러 가봅시다.package com.example.tdd_study.product.dto;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
public class AddProductRequest {
private String name;
private int price;
public AddProductRequest(String name, int price) {
this.name = name;
this.price = price;
}
}@RestController와 @PostMapping, @RequestBody가 사용됩니다.package com.example.tdd_study.product;
import com.example.tdd_study.product.dto.AddProductRequest;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
@PostMapping("/products")
public void register(@RequestBody AddProductRequest request) {
productService.register(request.getName(), request.getPrice());
}
}Gson 라이브러리를 사용하면 자바 객체를 JSON 문자열로 편하게 바꿀 수 있어 하드코딩을 피할 수 있습니다.
(build.gradle에 com.google.code.gson:gson 의존성을 추가하면 좋습니다. 없다면 Jackson을 써도 됩니다.)dependencies {
// ... 기존 의존성 생략
implementation 'com.google.code.gson:gson:2.8.9' // Gson 라이브러리 추가
}package com.example.tdd_study.product;
import com.example.tdd_study.product.dto.AddProductRequest;
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.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import com.google.gson.Gson;
@WebMvcTest(ProductController.class)
class ProductControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private ProductService productService;
@Test
void 상품_등록_API_검증() throws Exception {
// given
AddProductRequest request = new AddProductRequest("라떼", 5000);
Gson gson = new Gson();
String jsonContent = gson.toJson(request); // 객체 -> JSON 자동 변환
// when & then
mockMvc.perform(MockMvcRequestBuilders.post("/products")
.contentType(MediaType.APPLICATION_JSON)
.content(jsonContent))
.andExpect(MockMvcResultMatchers.status().isOk()); // 200 OK 확인
// verify를 통해 실제로 Service가 호출되었는지 확인 가능
// verify(productService).register("라떼", 5000);
}
}@RequestBody가 아주 중요한 역할을 합니다.
이 어노테이션은 택배 기사님과 같습니다.
사용자가 보낸 JSON 상자(데이터)를 뜯어서 자바 객체(AddProductRequest)에 알맞게 넣어줍니다.
만약 이 어노테이션을 빼먹으면 요청 데이터가 다 null로 들어오니 주의해야 합니다.
다음 시간에는Mockito를 활용한 의존성 분리 심화편을 다루겠습니다.