@EnableJpaAuditing + MockMVC 테스트 할 때의 주의사항

Kim Dong Kyun·2023년 7월 21일
1
post-thumbnail

엔티티에게 일괄적으로 시간을 찍어주기 위해서, 다음과 같은 방법이 우리에게 익숙합니다.

1. 엔티티

@Entity
@Table(name = "USERS")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User extends TimeStamped {
...
}

2. 상위 추상 클래스 TimeStamped

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class TimeStamped {
    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdAt;

    @LastModifiedDate
    @Column
    private LocalDateTime modifiedAt;
}

3. 그리고 위를 프로젝트에서 사용하겠어요! 어노테이션

@SpringBootApplication
@EnableJpaAuditing
public class ShoppingmallprojectApplication {

    public static void main(String[] args) {
        SpringApplication.run(ShoppingmallprojectApplication.class, args);
    }


}

많이들 사용하는 방법입니다.

그러나, 위와 같은 방식은 테스트 할 때 문제가 될 수 있습니다. 에러 메시지를 한번 볼까요?

... (매우 긴 에러메시지)

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaMappingContext': JPA metamodel must not be empty
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1751)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:599)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:521)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200)
	at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:365)
	... 99 more
Caused by: java.lang.IllegalArgumentException: JPA metamodel must not be empty
	at org.springframework.util.Assert.notEmpty(Assert.java:479)
	at org.springframework.data.jpa.mapping.JpaMetamodelMappingContext.<init>(JpaMetamodelMappingContext.java:58)
	at org.springframework.data.jpa.repository.config.JpaMetamodelMappingContextFactoryBean.createInstance(JpaMetamodelMappingContextFactoryBean.java:68)
	at org.springframework.data.jpa.repository.config.JpaMetamodelMappingContextFactoryBean.createInstance(JpaMetamodelMappingContextFactoryBean.java:44)
	at org.springframework.beans.factory.config.AbstractFactoryBean.afterPropertiesSet(AbstractFactoryBean.java:142)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1797)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1747)
	... 106 more

참 깁니다. 우리가 봐야 할 것은 어느부분인지? 라고 하면 바로

Caused by: java.lang.IllegalArgumentException: JPA metamodel must not be empty

이부분입니다. JPA의 메타모델은 empty 해선 안된다고 하네요?


JPA Metamodel?

참고 블로그

자바가 제공하는 "어노테이션 프로세서" 기능을 사용하면 어노테이션을 분석해서 클래스를 생성 할 수 있습니다. 이를 이용해서 Entity의 Criteria 클래스를 생성해요.

예를 들어, User엔티티의 Criteria 클래스는 User_ 와 같이 생성되는데, 이것이 바로 메타 모델입니다. 이를 이용하면 온전히 코드를 이용해서 쿼리를 작성 가능합니다 ("String" 형태로 작성 시에 오타나, 잦은 변경이 있을 경우 매우 곤란하니까)

// Criteria 쿼리
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> query = cb.createQuery(Member.class);

// 루트 클래스(조회를 시작할 클래스)
Root<Member> m = query.from(Member.class);

// 쿼리 생성
CriteriaQuery<Member> cq = query.select(m).where(cb.equal("username"), "kim"));

List<Member> resultList = em.createQuery(cq).getResultList();
  • m.get("username") -> m.get(Member_.username) 위와 같은 형태로 변경 가능한것이죠!

그래서 이게 왜 문젠데?

테스트를 돌릴 때는 기본적으로 Application 이 부트되야 합니다!!

그런데,

우리는 @WebMVCTest 어노테이션을 사용해서 테스트에 필요한 빈들만 불러오도록 했죠?

따라서 JPA 메타 모델이 온전히 만들어지지 않은 것입니다.


해결 방법 알려주세요!

  • 아주 간단합니다. 상위 추상 클래스에 어노테이션을 붙이기!
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@EnableJpaAuditing
public class TimeStamped {
    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdAt;

    @LastModifiedDate
    @Column
    private LocalDateTime modifiedAt;
}

끝!

하지만 이런 방법도 있긴 합니다.

@WebMvcTest(CartController.class)
@MockBean(JpaMetamodelMappingContext.class)
public class CartControllerTest {

메타모델 맵핑 컨텍스트를 의도적으로 주입해주는 방법이요. 하지만 모든 컨트롤러에 붙이긴 귀찮죠? 안붙이는걸로!

0개의 댓글