
HashMap을 사용했다는 점입니다.build.gradle 파일에 JPA와 H2 데이터베이스 라이브러리를 추가해야 합니다.
이것들이 없으면 @Entity나 JpaRepository 같은 기능을 사용할 수 없습니다.dependencies 블록에 아래 두 줄을 추가하고, 꼭 dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// [추가] JPA와 H2 데이터베이스
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'
}
HashMap이나 persistence 변수 등은 모두 Product 클래스를 DB 테이블과 매핑되도록 변경합니다.
@Entity, @Id, @GeneratedValue 어노테이션을 추가합니다.
(빨간 줄이 뜬다면 import가 제대로 되었는지 확인해 주세요. jakarta.persistence.* 패키지입니다.)package com.example.tdd_study.product;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.util.Assert;
@Entity
@Table(name = "products")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private int price;
public Product(String name, int price) {
Assert.hasText(name, "상품명은 필수입니다.");
Assert.isTrue(price > 0, "상품 가격은 0보다 커야 합니다.");
Assert.isTrue(price % 1000 == 0, "상품 가격은 1000원 단위여야 합니다.");
this.name = name;
this.price = price;
}
// (중요) 기존의 assignId 메서드는 삭제합니다. DB가 ID를 자동 생성해주기 때문입니다.
}ProductRepository 클래스 내용을 interface로 변경합니다.
기존에 있던 persistence 변수나 save 메서드 구현 코드는 더 이상 필요하지 않습니다.package com.example.tdd_study.product;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
// 코드가 비어있어도 괜찮습니다. JpaRepository가 save, findById 등을 대신 해줍니다.
}ProductService에서 컴파일 에러가 날 수 있습니다.
JPA의 save 메서드는 저장된 엔티티를 반환하지만, 기존 코드는 void였기 때문일 수 있습니다.
하지만 보통은 메서드 이름이 같아 큰 수정 없이 넘어갑니다.ProductServiceTest.java 파일입니다.Error: ProductRepository is abstract; cannot be instantiated
ProductRepository가 클래스여서 new ProductRepository()가 가능했지만, 이제는 new를 사용할 수 없기 때문입니다.
이 문제를 해결하기 위해 package com.example.tdd_study.product;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.verify;
import static org.mockito.ArgumentMatchers.any;
@ExtendWith(MockitoExtension.class) // Mockito 기능을 쓰겠다고 선언
class ProductServiceTest {
@Mock // 가짜 Repository 생성
private ProductRepository productRepository;
@InjectMocks // 가짜 Repository를 Service에 주입
private ProductService productService;
@Test
void 상품_등록_성공() {
// given
String name = "아메리카노";
int price = 4000;
// when
productService.register(name, price);
// then
// productRepository.save()가 호출되었는지 검증
verify(productRepository).save(any(Product.class));
}
}new 키워드 대신 @Mock을 사용하여 인터페이스의 가짜 구현체를 만들었기 때문에 테스트가 다시 정상 작동합니다.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.orm.jpa.DataJpaTest;
import static org.assertj.core.api.Assertions.assertThat;
@DataJpaTest
class ProductRepositoryTest {
@Autowired
private ProductRepository productRepository;
@Test
void 상품_저장() {
// given
Product product = new Product("아메리카노", 4000);
// when
Product savedProduct = productRepository.save(product);
// then
assertThat(savedProduct.getId()).isNotNull();
assertThat(savedProduct.getName()).isEqualTo("아메리카노");
}
}ProductRepositoryTest를 실행해 봅니다.
IntelliJ에서는 메서드 옆의 초록색 화살표(Run)를 클릭하면 됩니다.Tests passed 메시지가 뜬다면 성공입니다.
하지만 우리는 단순히 성공 여부뿐만 아니라 Hibernate: insert into products (name, price, id) values (?, ?, default)Product 객체를 분석했습니다.insert 쿼리를 실제로 날렸습니다.default(Auto Increment)로 생성되었습니다.@DataJpaTest가 없었다면, 우리는 DB를 켜고 테이블을 만드는 복잡한 과정을 매번 직접 해야 했을 것입니다.이제 Service는 Mock으로 빠르고 가볍게, Repository는 DB와 연결하여 확실하게 테스트하는이원화된 테스트 전략이 완성되었습니다. 다음 시간에는 사용자가 만나는 최종 관문,Controller 테스트와 API 검증에 대해 알아보겠습니다.
@MockBean, @DataJpaTest 등 스프링 부트가 제공하는 테스트 기능에 대한 설명입니다.