์ค๋์ ์ํ์ ๋ํ ์์ , ๋
ผ๋ฆฌ (soft) ์ญ์ ๊ธฐ๋ฅ์ ํ์๊ณ , ์คํ์๋ mokito ๋ผ๋ ์์กด์ฑ์ ์ถ๊ฐํ์ฌ Mock ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ์ฌ ํ
์คํธ๋ฅผ ์งํํ์๋ค.
์งํํ๋ฉด์ ๊ฒช์๋ ์ ์ ์ ๋ฆฌํด ๋ณด๊ฒ ์ต๋๋ค.
(์ํ์ ๋ํ ์์ ๊ธฐ๋ฅ์ ๋ํ ๋ด์ฉ์ ๋ณ๊ฒฝ๊ฐ์ง์ ๋ณํฉ์ ๋ํด ์์ ์์๋ค.)
๋ณ๊ฒฝ๊ฐ์ง : ์ํฐํฐ ํด๋์ค์์ ์์ ๋ฉ์๋ ์์ฑ
์ํฐํฐ ํฉํ ๋ฆฌ merge : ์ํ์ฑ์ด ์์
โก ๋ณ๊ฒฝ ๊ฐ์ง์ merge์ ๋ํด์๋ ๋ค์ ๋ธ๋ก๊ทธ์์ ๋ค๋ค์ผ๊ฒ ๋ค.
์ฒ์์ผ๋ก ๋ ผ๋ฆฌ ์ญ์ ๊ฐ ๋ฌด์์ธ์ง ์ ๋ ผ๋ฆฌ ์ญ์ ๋ฅผ ํด์ผ๋๋ ๊ฒ ์ธ์ง ์์๋ณด๊ฒ ์ต๋๋ค.
โก Controller ํด๋์ค
@DeleteMapping("/{id}")
public RsData softDeleteProduct(
@PathVariable(name = "id") Long productId) {
productService.softDeleteProduct(productId);
return RsData.of(DELETE_SUCCESS_PRODUCT, "delete");
}
- ๋ฑ๋ก๋ ์ํ์
selectํ๊ณ , ๋ฑ๋ก๋ ์ํ๊ณผ ์ฐ๊ด๋ ์ด๋ฏธ์ง๋ฅผselectํ์ฌ ๋ฑ๋ก๋ ์ํ์ ์กฐํํฉ๋๋ค.@DeleteMapping์ ์์ฒญ์ผ๋กdelete์ฟผ๋ฆฌ ๋ฌธ์ด ์คํ ๋๋ฉฐ, ๋ฐ์ดํฐ๋ฒ ์ด์ค์ row๊ฐ ์ค์ ๋ก ์ญ์ ๋๊ฒ ๋ฉ๋๋ค.
update ์ฟผ๋ฆฌ ๋ฌธ์ด ์คํ ๋๊ฒ ๋ฉ๋๋ค.
UPDATE์ฟผ๋ฆฌ ๋ฌธ์ด ์คํ ๋๊ฒ ๋๋ฉฐ, deleted_at์ ํ๋์ ์ญ์ ์์ฒญ์ด ๋ค์ด์จ ์๊ฐ์ ์ ์ฅํ๊ฒ ๋ฉ๋๋ค.
(deleted_atํ๋๋ ์ํฐํฐ ํด๋์ค์ ๋ฏธ๋ฆฌ ์์ฑํ์ฌ ๋ฐ์ดํฐ ๋ฒ ์ด์ค์ ๋งคํ ํด์ผ ๋ฉ๋๋ค.)
โก ๋
ผ๋ฆฌ ์ญ์ ๋ฅผ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์๋
@SQLDelete(sql = "UPDATE ์ํฐํฐ ํด๋์ค ์ด๋ฆ SET ํ๋ ์ด๋ฆ = CURRENT_TIMESTAMP where id = ?")์
์ํฐํฐ ํด๋์ค์ @SQLDelete() ์ด๋
ธํ
์ด์
์ ์์ฑํ์ฌ, ์ํฐํฐ ์ญ์ ๊ฐ ๋ฐ์ํ์์๋ delete ์ฟผ๋ฆฌ๋ฌธ๋์ ์ปค์คํ
ํ์ฌ ๋งคํ ํ ์ ์์ต๋๋ค.
๊ทธ๋์ ์ธ์ ๋
ผ๋ฆฌ ์ญ์ ๋ฅผ ์ฌ์ฉํด์ผ ๋ ๊น ?
โก ์ค์ ๋ฐ์ดํฐ๋ฅผ ์ญ์ ํ์ง์๊ณ ์ญ์ ์ด๋ ฅ์ ์ถ์ ํด์ผ ๋๋๊ฒฝ์ฐ, ๋ณต๊ตฌ ๊ฐ๋ฅ์ฑ์ด ํ์ํ ๊ฒฝ์ฐ ๋ฑ๋ฑ์ ์กฐ๊ฑด์์ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ๋ฅผ ๋ณดํธ ํ ์ ์์ต๋๋ค.
build.gradle์ ์๋์ ๊ฐ์ด ์์กด์ฑ ์ถ๊ฐ๊ฐ ํ์ ํฉ๋๋ค.Controller์ Service ๋จ์ ํ
์คํธ ํ๊ธฐ ์ํด ์ฌ์ฉํฉ๋๋ค.dependencies {
testImplementation 'org.mockito:mockito-core:5.2.0'
}
service ๋จ ์์ ์์กด์ฑ ์ฃผ์
์ ํ๋ ๊ฐ์ฒด๋ @Mock ์ด๋
ธํ
์ด์
์ ํตํด ๊ฐ์ง ๊ฐ์ฒด๋ก ๋ง๋ค์ด ์ค์ ์์ต๋๋ค.
@InjectMocks ์ด๋
ธํ
์ด์
์ ์ฃผ์
์ ๋ฐ์ ํด๋์ค๋ฅผ ์์ฑํ์ฌ ์ฌ์ฉ ํฉ๋๋ค.
@ExtendWith(MockitoExtension.class) // Mock ๊ฐ์ฒด ์ด๊ธฐํ๋ฅผ ์ํ ์ด๋
ธํ
์ด์
class ProductServiceImplTest {
@InjectMocks // ์ฃผ์
์ ๋ฐ์ ํด๋์ค
private ProductServiceImpl productService;
@Mock // ์ฃผ์
ํ ๊ฐ์ฒด
private ProductRepository productRepository;
@Mock // ์ฃผ์
ํ ๊ฐ์ฒด
private ImageRepository imageRepository;
@Test
void test() {
// ํ
์คํธ ์ฝ๋ ์์ฑ..
}
}
@DataJpaTest ์ด๋
ธํ
์ด์
์ ์คํ๋ง ๋ถํธ์ JPA ํ
์คํธ๋ฅผ ํ๊ธฐ ์ํด ์ฌ์ฉํ๋ ์ด๋
ธํ
์ด์
์
๋๋ค.H2 ๋ฐ์ดํฐ ๋ฒ ์ด์ค๋ฅผ default๋ก ํ
์คํธ ํ๊ฒ ๋ฉ๋๋ค.build.gradle ์ ์ถ๊ฐํ์ฌ ์ธ๋ฉ๋ชจ๋ฆฌ DB๋ฅผ ๊ตฌ์ถํด์ผ ํฉ๋๋ค.
dependencies {
runtimeOnly 'com.h2database:h2'
// ... ์ถ๊ฐ ์์กด์ฑ
}
@DataJpaTest
class ProductRepositoryTest {
@Autowired
private ProductRepository productRepository;
@DisplayName("์ํ ์ ์ฅ ํ์ธ")
@Test
void create_Product() {
Product product = Product.builder().productName("๊ฐ์์ง ์ฌ๋ฃ")
.price("25_000")
.content("์ ํต๊ธฐํ 1๋
๋จ์ ์ฌ๋ฃ์
๋๋ค.")
.build();
Product saveProduct = productRepository.save(product);
assertThat(saveProduct.getProductName())
.isEqualTo(product.getProductName());
}
}
Mock ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ์ฌ ํ ์คํธ๋ฅผ ์ฝ๋๋ฅผ ์ด๋ฒ ํ๋ก์ ํธ์์ ์ฒ์ ์๋ ํด๋ณด์์ต๋๋ค. ์ด์ํ๊ณ , ์ํฌ๋ฅด์ง๋ง ์ถ๊ฐ๋ก ๋ง๋ค ๊ธฐ๋ฅ๋ค์ ๋ํด ๊ณ์ ์ฌ์ฉํด๋ณด๋ฉฐ ์ต์ํด ์ง๋๊ฒ ๋ชฉํ ์ ๋๋ค.
https://velog.io/@sussa3007/Spring-JUnit-Mockito-%EA%B8%B0%EB%B0%98-Spring-%EB%8B%A8%EC%9C%84-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1 (mokito ํ ์คํธ ์ฐธ์กฐ ๋ธ๋ก๊ทธ)