1. 문제 상황 파악
- 컨트롤러 단위 테스트 코드 작성 (@WebMvcTest 사용)시 아래와 같은 에러 발생
// 에러가 발생한 테스트 코드
@WebMvcTest(CartController.class)
@AutoConfigureMockMvc(addFilters = false) // 필터 제외 (JWT 검증 제외)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@DisplayName("장바구니 컨트롤러 단위 테스트")
class CartControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private CartService cartService;
@Autowired
private ObjectMapper objectMapper;
// 기본 장바구니 컨트롤러의 URL
private static final String URL = "/api/carts";
// 생략 ...
}
BeanCreationException: Error creating bean with name 'jpaAuditingHandler'
- 에러 로그 분석 결과 “JpaAuditingHandler 빈을 생성하는데 오류가 생겼다” 는 뜻임을 확인
2. 문제 원인 파악
2-1. BeanCreationException 발생 이유
- JPA 관련 빈이 제대로 설정되지 않았기 때문에 발생함
- JPA Auditing을 사용하고 있을 때 자주 발생
- JPA 관련 설정이 누락되었거나 잘못되었을 때 나타남
2-2. 현재 상황에서 발생한 이유
- @WebMvcTest 를 사용한 테스트에서 발생하는 BeanCreationException
- 컨트롤러 단위 테스트 코드에서 서비스 레이어의 클래스를 모킹하여 사용하지만 CartService 내부에서 JPA 를 사용함!
- CartService 내부에서 JPA Repository를 사용하는 경우, 해당 레포지토리와 관련된 JPA 빈들이 필요하게 됨!
- 또한 프로젝트 내부에 아래와 같은 코드가 존재함
@SpringBootApplication
@EnableJpaAuditing
public class FitinsideApplication {
public static void main(String[] args) {
SpringApplication.run(FitinsideApplication.class, args);
}
}
@EnableJpaAuditing 어노테이션
- 어노테이션 설명
2024.09.07 - [Spring Boot] - [Spring Boot] Jpa Auditing (+ BaseEntity) 로 엔티티 관련 이벤트 자동 기록하기
- 어노테이션 삽입 이유: Jpa Auditing을 통해 엔티티 관련 생성, 수정일자를 자동으로 기록하게 설정하기 위해
⇒ @EnableJpaAuditing 어노테이션을 애플리케이션 클래스에 붙임으로써 모든 테스트가 JPA 관련 빈들을 필요로 하는 상태가 되어 문제가 발생한 것!
- 에러 발생 이유
- 프로젝트 내에서 Spring 컨테이너를 활용하는 테스트를 진행할 때, 가장 기본이 되는 Application 클래스가 항상 로드 됨
- Application 클래스에 @EnableJpaAuditing 어노테이션이 추가되어 있어, 모든 테스트가 JPA 관련 Bean들을 필요로 하는 상태가 되어버린 것
- 통합 테스트 ⇒ 전체 컨텍스트를 로드하고 JPA를 포함한 모든 Bean들을 주입받기 때문에 문제 없음
- @WebMvcTest를 사용하는 단위 테스트 ⇒ JPA 관련 Bean을 전혀 로드하지 않기 때문에, 여기서 에러가 발생함!
3. 해결 방법 및 결과 검증
3-1. @MockBean(JpaMetamodelMappingContext.class) 어노테이션으로 해결
3-1-1. @MockBean
- 스프링 프레임워크에서 주로 테스트를 위해 사용하는 어노테이션임
- 스프링의 테스트 컨텍스트에서 특정 빈을 mocking 할 때 사용
3-1-2. JpaMetamodelMappingContext
- JPA의 메타모델을 처리하는 컨텍스트
- JPA 사용 시 엔티티의 메타 정보를 제공하는 역할을 함
3-1-3. @MockBean(JpaMetamodelMappingContext.class)
- 스프링의 테스트 컨텍스트에 JpaMetamodelMappingContext 클래스를 모킹하여 주입함
- 아래의 코드와 동일한 내용임
@MockBean
private JpaMetamodelMappingContext jpaMetamodelMappingContext;
3-1-4. 테스트 코드에 반영하여 해결
@WebMvcTest(CartController.class)
@AutoConfigureMockMvc(addFilters = false)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
// JpaMetamodelMappingContext 클래스를 모킹하여 테스트 컨텍스트에 주입
@MockBean(JpaMetamodelMappingContext.class)
@DisplayName("장바구니 컨트롤러 단위 테스트")
class CartControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private CartService cartService;
@Autowired
private ObjectMapper objectMapper;
// 기본 장바구니 컨트롤러의 URL
private static final String URL = "/api/carts";
// 생략 ...
}
⇒ 단위 테스트가 통과됨을 확인!
3-2. JpaAuditingConfig 클래스로 해결
3-2-1. JpaAuditingConfig 클래스를 별도로 생성
@Configuration
@EnableJpaAuditing
public class JpaAuditingConfiguration {
}
별도의 클래스로 분리하여 JPA Auditing 활성화
1. 기본 애플리케이션 클래스에서 JPA 관련 빈들을 로드하지 않고도 JPA Auditing 기능 사용 가능
2. JPA 관련 설정을 분리하여, 테스트 시 불필요한 빈의 로드를 피할 수 있음!
⇒ @WebMvcTest 사용 시 JPA 관련 빈들이 로드되지 않기 때문에 테스트에서 발생하던 BeanCreationException을 피할 수 있음!
3-2-2. 결과 확인
- 테스트 코드는 기존의 코드로 유지
⇒ 단위 테스트가 통과됨을 확인!