반응형
반응형

개요

 Controller 클래스에 대한 테스트 코드 작성 시 @WebMvcTest 어노테이션을 사용하여 웹 레이어에 대한 단위 테스트를 한다. 웹 레이어만 테스트하므로 JPA 관련 빈을 사용할 일이 없으며, Mock 객체를 등록할 필요도 없었다. 하지만 테스트 코드를 실행하니 japMappingContext 빈을 생성하지 못했다는 런타임 예외가 발생했다.

 

예외 내용 일부
Error creating bean with name 'jpaAuditingHandler': Cannot resolve reference to bean 'jpaMappingContext' while setting constructor argument
...
Caused by: java.lang.IllegalArgumentException: JPA metamodel must not be empty

 

 이에 대한 해결책으로 아래와 같이 JpaMetamodelMappingContext 클래스를 Mock Bean으로 등록하면 된다고 하지만, 왜 굳이 사용하지도 않고, 테스트하지도 않는 Jpa 관련 클래스를 모킹하는지 이해가 되지 않았다. 삽질을 하는 절차를 밟아보도록 하자. @_@

@WebMvcTest(NameController.class)
@MockBean(JpaMetamodelMappingContext.class) // 간단한 해결책
class NameControllerTest {
	...
}

JpaMetamodelMappingContext 란?

JpaMetamodelMappingContext란?
 Spring Data JPA의 일부로서, 엔티티에 대한 매핑 정보를 제공하는 역할을 하는 하는 클래스. 즉, Spring 에서 JPA 엔티티의 메타데이터를 필요로 할때 해당 메타데이터를 제공해준다.

JpaMetamodelMappingContext 빈은 어디서 등록되나?

 디버깅을 통해 확인한 결과 DefaultListableBeanFactory 클래스의 beanDefinitionNames 필드에 JpaMetamodelMappingContext 에 대한 클래스 정보가 들어가는 있는 것을 확인했다. 테스트 실행 시 먼저 등록해야할 빈의 클래스 정보를 먼저 수집한 후 beanDefinitionNames 에 넣고있었다. 즉, 어떤 이유로 인해 JpaMetamodelMappingContext가 생성되어야할 빈 리스트로 포함된것이다.

 

 해당 작업이 끝나면 아래와 같이 beanDefinitionNames 에 들어있던 클래스 정보를 하나씩 읽어 빈을 생성하게 된다.

jpaMappingContext에 대한  빈을 생성하려는 코드

 

 

 그리고 이 메서드의 하단부에 아래와 같이 beanFactory에서 getBean을 통해 jpaMappingContext 에 대한 빈을 생성/조회 를 시도한다. 이때 예외가 발생하는데 내부적으로 JpaMetamodelMappingContext 빈을 생성 실패하면서 발생하게 된다.

 

getBean 도중 예외 발생

 

 

 이 빈에 대한 생성자를 보면 메서드의 models 값이 빈값으로 들어와 Assert 구문에 의해 JPA metamodel must not empty라는 예외가 발생하는 것을 알 수 있다.

JpaMetamodelMappingContext의 생성자 메서드

 

 

어찌됐든  jpaMappingContext 가 어떤 이유로 인해 빈 등록을 시도하고 있고, 이를 찾아낸다면 이 에러의 원인도 해결할 수 있게 되었다.


DefaultListableBeanFactory 클래스가 뭐야

DefaultListableBeanFactory  클래스는 빈 팩터리 클래스 중 하나이며, 코드레벨에서 빈을 수동으로 등록할 때 이 클래스를 사용하여 빈 정보를 주입하기도 한다. 즉, 일반적인 빈 팩터리 클래스이다.

 


누가 JpaMappingContext 빈을 추가했나?

 그럼 이 클래스를 어떤 클래스가 빈으로 추가하려 했는지 알아보기 위해 다시한번 디버깅의 늪으로 들어갔다. 그리고 이 문제의 원인을 찾았다. 바로 Application 클래스가 문제였다. 테스트 코드 실행 시 Application 클래스의 정보를 로드하면서 @EnableJpaAuditing 어노테이션이 동작하게 된 것이다. 그리고 이 어노테이션의 설정 정보로 인해 japMappingContext 빈을 등록하려 했던 것이다.

 

Application 클래스에 떡하니 있는 @EnableJpaAuditing

 

 이 어노테이션은 설정 클래스로 JpaAuditingRegistrar.class를 로드하고 있는데 이 클래스는 ImportBeanDefinitionRegistrar의 구현체 클래스이다. 어플리케이션이 로드되면 구현체 클래스의 registerBeanDefinitions 메서드가 호출되는데, 바로 이때 jpaMappingContext 빈 정보를 등록하고, 뒤이어 auditingHandler 빈 정보도 등록한다.

(특이하게도 auditingHandler 라는 빈 핸들러를 추가할 때 빈 이름은 getAuditingHandlerBeanName(), 클래스는 null 로 등록하고 있는데 이 메서드의 반환 값이 바로 jpaAuditingHandler 였다.)

 

 결론은 @EnableJpaAuditing 에 의해 jpaMappingContext 빈 생성 정보가 빈 팩토리로 들어갔다는 것이다.

 

jpaMappingContext 와  jpaAuditingHandler 빈 정보 등록

 

auditingHandlerBeanName 메서드

 


테스트를 실행하는데 왜 Application 클래스의 정보를 읽는거야?

 @WebMvcTest 를 사용하면 내부 동작에 의해 먼저 Application 클래스의 설정을 로드하고, 웹 레이어에 필요한 클래스를 스캔한다. 때문이다. 실제로 이 클래스에 있는 @EnableJpaAuditing 어노테이션을 제거하니 테스트코드에서 오류는 발생하지 않았다.

 


결론

  JPA 관련 예외가 발생한 이유는 Application 클래스에서 사용하고 있던 @EnableJpaAuditing 에 의해 jpaMappingContext 빈 생성 정보를 빈 팩토리가 로드했기 때문이었다. 이로 인해 테스트 코드에서 빈 생성을 시도하게 되고 필요한 JPA 설정들은 추가하지 않은 상태에서 필요한 JpaMetamodelMappingContext 빈을 생성하지 못해 발생했다. 

 JpaMetamodelMappingContext 를 MockBean으로 등록하면 문제가 해결됐던 것도 이해가 갔다.

 만약 누군가 이러한 에러를 마주하게 된다면 테스트 코드 어딘가에 JPA 관련 설정이 있는지와 Application 클래스에 관련 어노테이션이 있는지를 꼭 하길 바란다.

 

 

반응형

+ Recent posts