Test Code - dnd-side-project/dnd-11th-5-backend GitHub Wiki
Warning
νμ¬ @Transactional
λ‘ μν νΈλμμ
κ²½κ³ κ΄λ ¨ μ΄μκ° μ‘΄μ¬ν¨!
κ΄λ ¨ pr : https://github.com/dnd-side-project/dnd-11th-5-backend/pull/155
[IntelliJ] Live Template μμ±
- ν μ€νΈ κ°μ²΄ μμ± λΌμ΄λΈλ¬λ¦¬
-
ν
μ€νΈ μ λ΅ κ΄λ ¨
- https://hyunminh.github.io/test-coverage/ (μμ£Ό μ΄ν΄κ° μ λλ κΈμ λλ€...)
-
https://github.com/cheese10yun/spring-guide/blob/master/docs/test-guide.md#integrationtest
-
xxxTestSupports
ꡬνν λ, λ§μ λΆλΆμ μ°Έκ³ νμμ΅λλ€.
-
- κ·Έ μΈ
μ°Έκ³ μλ£μ λ§μ λΆλΆμ μ°Έκ³ νμμΌλ―λ‘, ν΄λΉ μλ£λ₯Ό λ¨Όμ μ½λ κ²μ μΆμ²
-
[IntelliJ] Live Template μμ± μ ν΅ν΄ μλμΌλ‘ μ½λλ₯Ό μμ±ν μ μμ΅λλ€.
-
given
: ν μ€νΈλ₯Ό μν λͺ¨λ μ€λΉ λ¨κ³ -
when
: μ΄λ€ νμλ₯Ό νμ λ -
then
: μ΄λ ν κ²°κ³Όκ° λμμΌ νλ€.
@DisplayName("μ£Όλ¬Έ μμ± μ μ£Όλ¬Έ λ±λ‘ μκ°μ κΈ°λ‘νλ€.")
@Test
public void registeredDateTime() {
// given
LocalDateTime registeredDateTime = LocalDateTime.now();
List<Product> products = List.of(
createProduct("001", 1000),
createProduct("002", 2000)
);
// when
Order order = Order.create(products, registeredDateTime);
// then
assertThat(order.getRegisteredDateTime()).isEqualTo(registeredDateTime);
}
- μ£Όλ‘ λλ©μΈ ν μ€νΈ μ§νμ μ¬μ©
-
ν΄νΌ μΌμ΄μ€μ μμΈ μΌμ΄μ€ λ κ°μ§ κ²½μ°μ λν΄ λͺ¨λ ν
μ€νΈλ₯Ό μμ±νλ€.
- κ²½κ³κ° ν μ€νΈλ₯Ό μ§ννλ€.
@DisplayName("μ¬κ³ λ₯Ό μ£Όμ΄μ§ κ°μλ§νΌ μ°¨κ°ν μ μλ€.")
@Test
public void deductQuantity() {
// given
Stock stock = Stock.create("001", 1);
int quantity = 1;
// when
stock.deductQuantity(quantity);
// then
assertThat(stock.getQuantity()).isZero();
}
@DisplayName("μ¬κ³ λ³΄λ€ λ§μ μμ μλμΌλ‘ μ°¨κ° μλνλ κ²½μ° μμΈκ° λ°μνλ€.")
@Test
public void deductQuantity2() {
// given
Stock stock = Stock.create("001", 1);
int quantity = 2;
// when
// then assertThatThrownBy(() -> stock.deductQuantity(quantity))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("μ°¨κ°ν μ¬κ³ μλμ΄ μμ΅λλ€.");
}
-
IntergrationTestSupport
λ₯Ό extends νμ¬ μ¬μ©νλ, mocking λ±μΌλ‘ μλΉμ€ νκ²½μ΄ λ¬λΌμ§λ€λ©΄ λ°λ‘ μμ±νλ€. -
μ°Έκ³ μλ£ : λμ¦, ν°ν€μ Classic TDD VS Mockist TDD
- classic μΌλ‘ μ£Όλ‘ μ§ννλ, μλΉμ€ ν
μ€νΈμμ λ€λ₯Έ μλΉμ€λ νΉμ λ무 볡μ‘ν λ°μ΄ν°μ
ν
μ΄ μλ€λ©΄ mokup νλ€.
-
MockupTestSupport
λ₯Ό extends νλ€.
-
- classic μΌλ‘ μ£Όλ‘ μ§ννλ, μλΉμ€ ν
μ€νΈμμ λ€λ₯Έ μλΉμ€λ νΉμ λ무 볡μ‘ν λ°μ΄ν°μ
ν
μ΄ μλ€λ©΄ mokup νλ€.
- κ²μ¦ν΄μΌ νλ κ² : client μμ λμ΄μ¨ κ°μ λν validation
- νμ λ μ΄μ΄λ₯Ό mocking νμ¬ μ²λ¦¬νλ€.
-
ControllerTestSupport
λ₯Ό extends νμ¬ μ¬μ©νλ€.
@ActiveProfiles("test")
@WebMvcTest(controllers = {
HelloController.class, // μ¬μ©νλ 컨νΈλ‘€λ¬ μ¬κΈ°μ μΆκ°
})
public abstract class ControllerTestSupport {
@Autowired
protected MockMvc mockMvc;
@Autowired
protected ObjectMapper objectMapper;
// λͺ¨νΉν λΉ μΆκ°
// @MockBean
// protected ProductService productService;
}
class ProductControllerTest extends ControllerTestSupport {
@DisplayName("μ κ· μνμ λ±λ‘νλ€.")
@Test
public void createProduct() throws Exception {
// given
ProductCreateRequest request = ProductCreateRequest.builder()
.type(ProductType.HANDMADE)
.sellingStatus(ProductSellingStatus.SELLING)
.name("λ λͺ¬μμ΄λ")
.price(4000)
.build();
// when // then
mockMvc.perform(
post("/api/v1/products/new")
.content(objectMapper.writeValueAsString(request))
.contentType(MediaType.APPLICATION_JSON)
)
.andDo(print()) // μμΈν λ‘κ·Έ 보기
.andExpect(status().isOk());
}
@DisplayName("μ κ· μνμ λ±λ‘ν λ μν νμ
μ νμκ°μ΄λ€.")
@Test
public void createProductWithoutType() throws Exception {
// given
ProductCreateRequest request = ProductCreateRequest.builder()
.sellingStatus(ProductSellingStatus.SELLING)
.name("λ λͺ¬μμ΄λ")
.price(4000)
.build();
// when // then
mockMvc.perform(
post("/api/v1/products/new")
.content(objectMapper.writeValueAsString(request))
.contentType(MediaType.APPLICATION_JSON)
)
.andDo(print()) // μμΈν λ‘κ·Έ 보기
.andExpect(status().isBadRequest())
// μλ΅μΌλ‘ λ΄λ €μ¨ json κ°μ²΄ κ²μ¦νκΈ°
.andExpect(jsonPath("$.code").value("400"))
.andExpect(jsonPath("$.status").value("BAD_REQUEST"))
.andExpect(jsonPath("$.message").value("μν νμ
μ νμμ
λλ€."))
.andExpect(jsonPath("$.data").isEmpty())
;
}
@DisplayName("μ κ· μνμ λ±λ‘ν λ μν μ΄λ¦μ νμκ°μ΄λ€.")
@Test
void createProductWithoutName() throws Exception {
// given
ProductCreateRequest request = ProductCreateRequest.builder()
.type(ProductType.HANDMADE)
.sellingStatus(ProductSellingStatus.SELLING)
.price(4000)
.build();
// when // then
mockMvc.perform(
post("/api/v1/products/new")
.content(objectMapper.writeValueAsString(request))
.contentType(MediaType.APPLICATION_JSON)
)
.andDo(print())
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.code").value("400"))
.andExpect(jsonPath("$.status").value("BAD_REQUEST"))
.andExpect(jsonPath("$.message").value("μν μ΄λ¦μ νμμ
λλ€."))
.andExpect(jsonPath("$.data").isEmpty())
;
}
@DisplayName("μ κ· μνμ λ±λ‘ν λ μν κ°κ²©μ μμμ΄λ€.")
@Test
public void createProductWithZeroPrice() throws Exception {
// given
ProductCreateRequest request = ProductCreateRequest.builder()
.type(ProductType.HANDMADE)
.sellingStatus(ProductSellingStatus.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())
// μλ΅μΌλ‘ λ΄λ €μ¨ json κ°μ²΄ κ²μ¦νκΈ°
.andExpect(jsonPath("$.code").value("400"))
.andExpect(jsonPath("$.status").value("BAD_REQUEST"))
.andExpect(jsonPath("$.message").value("μν κ°κ²©μ μμμ¬μΌ ν©λλ€."))
.andExpect(jsonPath("$.data").isEmpty())
;
}
@DisplayName("ν맀 μνμ μ‘°ννλ€.")
@Test
public void getSellingProducts() throws Exception {
// given
List<ProductResponse> result = List.of();
when(productService.getSellingProducts()).thenReturn(result);
// when // then
mockMvc.perform(
get("/api/v1/products/selling")
)
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value("200"))
.andExpect(jsonPath("$.status").value("OK"))
.andExpect(jsonPath("$.message").value("OK"))
// λ€λ₯Έ λΆλΆμ serviceμ repository μμ κ²μ¦μ΄ λμμΌλ―λ‘
// controller μμλ μνλ νν, (μ¬κΈ°μ ) Array ννμΈμ§ μ λλ‘ κ²μ¦νλ€.
.andExpect(jsonPath("$.data").isArray());
}
}
- κ°λ₯νλ€λ©΄ mockup μμ΄ μ§ννλ€.
- κ·Έλ¬λ λ€λ₯Έ μλΉμ€ νΉμ λ무 볡μ‘ν λ°μ΄ν° μ ν μ΄λΌλ©΄ mockup νλ€.
- mocking μμ΄ μ§ννλ€λ©΄
IntergrationTestSupport
λ₯Ό extends νμ¬ μ¬μ©νκ³ , mocking νμ¬ μ¬μ©νλ€λ©΄ ν΄λΉ ν΄λμ€μ λν΄μλ λ°λ‘ νκ²½μ μ€μ νλ€.
π₯ μ£Όμμ¬ν
κ° ν μ€νΈμ λν rollback νΈμλ₯Ό μν΄,
IntergrationTestSupport
μ@Transactional
μ λ Έν μ΄μ μ μΆκ°νμμΌλ μ΄ κ²½μ° service layerμμ νΈλμμ μ λΉΌλ¨Ήμμ κ²½μ° μ μ£Όμνμ΄μΌ ν©λλ€.
- μ§μ μ§ query, queryDSL μ λν ν μ€νΈλ₯Ό μ°μ μΌλ‘ νλ€.
- 결과보λ€λ μΏΌλ¦¬κ° μ΄λ»κ² λκ°λμ§ νμΈνλ κ±Έ μ°μ μΌλ‘ νλ€.