스프링 데이터 리포지토리를 테스트하는 방법?


136

UserRepositorySpring Data의 도움으로 저장소 (예 :)를 만들고 싶습니다 . 나는 스프링 데이터에 익숙하지 않지만 (스프링은 아님)이 자습서를 사용합니다 . 데이터베이스를 다루기 위해 제가 선택한 기술은 JPA 2.1과 Hibernate입니다. 문제는 그러한 저장소에 대한 단위 테스트를 작성하는 방법에 대한 단서가 없다는 것입니다.

create()예를 들어 방법을 보자 . 테스트를 먼저 할 때 단위 테스트를 작성해야합니다. 여기에서 세 가지 문제가 발생합니다.

  • 먼저 EntityManager존재하지 않는 UserRepository인터페이스 구현에 모의를 어떻게 주입 합니까? Spring Data는이 인터페이스를 기반으로 구현을 생성합니다.

    public interface UserRepository extends CrudRepository<User, Long> {}

    그러나 EntityManager모의 및 기타 모의 를 사용하도록 강제하는 방법을 모르겠습니다 . 구현을 직접 작성했다면에 대한 setter 메소드 EntityManager를 사용하여 단위 테스트에 모의를 사용할 수 있습니다. (실제 데이터베이스 연결에 관해서는, 나는이 JpaConfiguration주석 클래스 @Configuration@EnableJpaRepositories프로그램을 위해 콩을 정의, DataSource, EntityManagerFactory, EntityManager등 -하지만 저장소 테스트 친화적 있어야하고,이 일을 오버라이드 (override)를 허용한다).

  • 둘째, 상호 작용을 테스트해야합니까? 내가 어떤 방법을 파악하는 것은 어렵다 EntityManagerQuery(그와 유사한 호출로되어있다 verify(entityManager).createNamedQuery(anyString()).getResultList();)가 구현을 작성 누군지하지 않기 때문에.

  • 셋째, 스프링 데이터 생성 메소드를 먼저 테스트해야합니까? 아시다시피, 타사 라이브러리 코드는 단위 테스트를 거치지 않아야합니다. 개발자가 직접 작성한 코드 만 단위 테스트를 거쳐야합니다. 나는 내 망신 시켰습니다 주사 I을 어떻게,하는 I 구현을 작성하는 것, 내 저장소에 대한 사용자 지정 방법의 몇 가지있다 말 : 그게 사실이라면, 그것은 여전히 첫 번째 질문의 장면에 등을 제공 EntityManager하고 Query생성 마지막으로, 저장소?

참고 : 통합 및 단위 테스트 를 모두 사용하여 리포지토리를 테스트합니다. 통합 테스트를 위해 HSQL 인 메모리 데이터베이스를 사용하고 있으며 단위 테스트에 데이터베이스를 사용하고 있지 않습니다.

그리고 아마도 네 번째 질문은 통합 테스트에서 올바른 객체 그래프 생성 및 객체 그래프 검색을 테스트하는 것이 맞습니까?

업데이트 : 오늘 모의 주입 실험을 계속했습니다. 모의 주입을 허용하는 정적 내부 클래스를 만들었습니다.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@Transactional
@TransactionConfiguration(defaultRollback = true)
public class UserRepositoryTest {

@Configuration
@EnableJpaRepositories(basePackages = "com.anything.repository")
static class TestConfiguration {

    @Bean
    public EntityManagerFactory entityManagerFactory() {
        return mock(EntityManagerFactory.class);
    }

    @Bean
    public EntityManager entityManager() {
        EntityManager entityManagerMock = mock(EntityManager.class);
        //when(entityManagerMock.getMetamodel()).thenReturn(mock(Metamodel.class));
        when(entityManagerMock.getMetamodel()).thenReturn(mock(MetamodelImpl.class));
        return entityManagerMock;
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        return mock(JpaTransactionManager.class);
    }

}

@Autowired
private UserRepository userRepository;

@Autowired
private EntityManager entityManager;

@Test
public void shouldSaveUser() {
    User user = new UserBuilder().build();
    userRepository.save(user);
    verify(entityManager.createNamedQuery(anyString()).executeUpdate());
}

}

그러나이 테스트를 실행하면 다음과 같은 스택 추적이 제공됩니다.

java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.CacheAwareContextLoaderDelegate.loadContext(CacheAwareContextLoaderDelegate.java:99)
at org.springframework.test.context.DefaultTestContext.getApplicationContext(DefaultTestContext.java:101)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:109)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:75)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:319)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:212)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:289)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:291)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:232)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:89)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:175)
at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:77)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:195)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:63)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userRepository': Error setting property values; nested exception is org.springframework.beans.PropertyBatchUpdateException; nested PropertyAccessExceptions (1) are:
PropertyAccessException 1: org.springframework.beans.MethodInvocationException: Property 'entityManager' threw exception; nested exception is java.lang.IllegalArgumentException: JPA Metamodel must not be null!
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1493)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1197)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:304)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:300)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:195)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:684)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:760)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482)
    at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:121)
    at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:60)
    at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.delegateLoading(AbstractDelegatingSmartContextLoader.java:100)
    at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.loadContext(AbstractDelegatingSmartContextLoader.java:250)
    at org.springframework.test.context.CacheAwareContextLoaderDelegate.loadContextInternal(CacheAwareContextLoaderDelegate.java:64)
    at org.springframework.test.context.CacheAwareContextLoaderDelegate.loadContext(CacheAwareContextLoaderDelegate.java:91)
    ... 28 more
Caused by: org.springframework.beans.PropertyBatchUpdateException; nested PropertyAccessExceptions (1) are:
PropertyAccessException 1: org.springframework.beans.MethodInvocationException: Property 'entityManager' threw exception; nested exception is java.lang.IllegalArgumentException: JPA Metamodel must not be null!
    at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:108)
    at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:62)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1489)
    ... 44 more

답변:


118

tl; dr

간단히 말해-간단한 이유로 스프링 데이터 JPA 저장소를 단위 테스트하는 방법은 없습니다. 저장소를 부트 스트랩하기 위해 호출하는 JPA API의 모든 부분을 조롱하는 것은 번거로운 방법입니다. 단위 테스트는 구현 코드를 직접 작성하지 않기 때문에 (여기서는 사용자 지정 구현에 대해서는 아래 단락 참조) 단위 테스트는 그다지 의미가 없으므로 통합 테스트가 가장 합리적인 방법입니다.

세부

우리는 유효하지 않은 파생 쿼리 등이없는 앱만 부트 스트랩 할 수 있도록 많은 사전 검증 및 설정을 수행합니다.

  • CriteriaQuery쿼리 메서드에 오타가 포함되지 않도록 파생 쿼리에 대한 인스턴스를 만들고 캐시 합니다. 이를 위해서는 meta.model뿐만 아니라 Criteria API를 사용해야합니다.
  • Google EntityManagerQuery쿼리 구문 유효성 검사를 효과적으로 트리거 인스턴스 확인합니다.
  • 우리는 검사 Metamodel준비를 처리 도메인 유형에 대해 메타 데이터 것은 새로운 검사 등

수작업으로 작성된 리포지토리에서 연기 할 수있는 모든 것 (잘못된 쿼리 등으로 인해 런타임에 응용 프로그램이 중단 될 수 있음)

당신이 그것에 대해 생각한다면, 당신의 저장소를 위해 작성한 코드가 없기 때문에 단위 테스트 를 작성할 필요가 없습니다 . 기본 버그를 파악하기 위해 테스트베이스에 의존 할 필요가 없습니다 (여전히 버그가 발생하면 자유롭게 티켓 을 올리십시오 ). 그러나 지속성 계층의 두 가지 측면을 도메인과 관련된 측면으로 테스트하려면 통합 테스트가 필요합니다.

  • 엔터티 매핑
  • 쿼리 의미론 (구문은 각 부트 스트랩 시도에서 확인됩니다).

통합 테스트

이것은 일반적으로 ApplicationContext이미 테스트 컨텍스트 프레임 워크를 통해 (이미와 같이) Spring을 부트 스트랩하거나 데이터베이스를 미리 채 웁니다 ( EntityManager또는 인스턴스를 통해 또는 인스턴스를 통해 또는 일반을 통해) SQL 파일)을 찾은 다음 쿼리 방법을 실행하여 결과를 확인하십시오.

사용자 정의 구현 테스트

저장소의 사용자 정의 구현 부분 은 Spring Data JPA에 대해 알 필요가없는 방식으로 작성됩니다 . 그들은 EntityManager주사 를 얻는 평범한 봄 콩입니다 . 당신은 물론 그와의 상호 작용을 조롱하지만, 정직하려고 싶어 수, 단위 테스트하는 JPA뿐만 아니라이 indirections 꽤 많은 작품으로 우리에게 너무 즐거운 경험하지 않았다 ( EntityManager-> CriteriaBuilder, CriteriaQuery등) 때문에 모의를 반환하는 모의로 끝납니다.


5
메모리 내 데이터베이스 (예 : h2)와의 통합 테스트의 작은 예에 대한 링크가 있습니까?
Wim Deblauwe

7
여기 예제 는 HSQLDB를 사용합니다. H2로 전환하는 것은 기본적으로의 종속성을 교환하는 문제입니다 pom.xml.
Oliver Drotbohm

3
고맙지 만 데이터베이스를 미리 채우거나 실제로 데이터베이스를 검사하는 예제를보고 싶었습니다.
Wim Deblauwe

1
"방법으로 작성된"뒤에있는 링크는 더 이상 작동하지 않습니다. 어쩌면 업데이트 할 수 있습니까?
Wim Deblauwe

1
따라서 사용자 정의 구현에도 단위 테스트 대신 통합 테스트를 사용하도록 제안합니까? 그리고 단위 테스트를 전혀 쓰지 않습니까? 다시 한번 확인하기 위해. 예라면 괜찮습니다. 이유를 이해합니다 (모든 것을 조롱하기에는 너무 복잡함). JPA 테스트를 처음 사용하므로 이해하고 싶습니다.
Ruslan Stelmachenko

48

Spring Boot + Spring Data를 사용하면 매우 쉬워졌습니다.

@RunWith(SpringRunner.class)
@DataJpaTest
public class MyRepositoryTest {

    @Autowired
    MyRepository subject;

    @Test
    public void myTest() throws Exception {
        subject.save(new MyEntity());
    }
}

@heez의 솔루션은 전체 컨텍스트를 제공하므로 JPA + 트랜잭션이 작동하는 데 필요한 것만 가져옵니다. 위의 솔루션은 클래스 패스에서 찾을 수있는 메모리 테스트 데이터베이스를 가져옵니다.


7
이것은입니다 통합 테스트가 아닌 단위 영업 이익은 언급 한 테스트
이오 쿠 찰스

16
@IwoKucharski. 당신은 용어에 대해 옳습니다. 그러나 Spring Data가 인터페이스를 구현한다고 가정하면 Spring을 사용하기가 어려워지고 그 시점에서 통합 테스트가됩니다. 이와 같은 질문을한다면 아마 용어에 대해 생각하지 않고 단위 테스트를 요구했을 것입니다. 따라서 나는 그것이 문제의 주된, 또는 중심적인 지점으로 보지 않았다.
Markus T

@RunWith(SpringRuner.class)에 이미 포함되어 있습니다 @DataJpaTest.
Maroun

@ IwoKucharski, 이것이 왜 단위 테스트가 아닌 통합 테스트입니까?
user1182625

@ user1182625 @RunWith(SpringRunner.class는 스프링 컨텍스트를 시작하므로 여러 장치 간의 통합을 확인합니다. 단위 테스트는 단일 단위-> 단일 클래스를 테스트합니다. 그런 다음 MyClass sut = new MyClass();sut 객체 를 작성 하고 테스트합니다 (Sut = 테스트 대상 서비스)
Iwo Kucharski

21

이것은 너무 늦게 올 수 있지만이 목적을 위해 무언가를 작성했습니다. 내 라이브러리는 기본적인 crud 저장소 방법을 모색하고 쿼리 방법의 대부분의 기능을 해석합니다. 고유 한 고유 쿼리에 기능을 주입해야하지만 나머지는 자동으로 수행됩니다.

구경하다:

https://github.com/mmnaseri/spring-data-mock

최신 정보

이것은 현재 Maven 중심에 있으며 꽤 좋은 모양입니다.


16

Spring Boot를 사용 @SpringBootTest하는 경우 간단히 ApplicationContext스택 로드 (스택 트레이스가 짖는 것) 를로드 하는 데 사용할 수 있습니다 . 이를 통해 스프링 데이터 리포지토리에서 자동 와이어 링 할 수 있습니다. @RunWith(SpringRunner.class)스프링 별 주석이 선택되도록 추가 해야합니다.

@RunWith(SpringRunner.class)
@SpringBootTest
public class OrphanManagementTest {

  @Autowired
  private UserRepository userRepository;

  @Test
  public void saveTest() {
    User user = new User("Tom");
    userRepository.save(user);
    Assert.assertNotNull(userRepository.findOne("Tom"));
  }
}

봄 부팅 테스트에 대한 자세한 내용은 해당 문서를 참조하십시오 .


이것은 꽤 좋은 예이지만 내 견해로는 단순합니다. 이 테스트가 실패 할 수도있는 상황이 있습니까 ??
HopeKing

이 자체는 아니지만 Predicates (내 유스 케이스) 를 테스트하고 싶었다고 가정합니다 .
heez

1
나를 위해 저장소는 항상 null입니다. 어떤 도움?
Atul Chaudhary

이것이 최고의 답변입니다. 이 방법으로 CrudRepo, 엔티티 및 엔티티의 테이블을 작성하는 DDL 스크립트를 테스트 할 수 있습니다.
MirandaVeracruzDeLaHoyaCardina

나는 이와 같은 테스트를 작성했습니다. 리포지토리 구현이 jdbcTemplate을 사용할 때 완벽하게 작동합니다. 그러나 스프링 데이터에 대한 구현을 변경하면 (Repository에서 인터페이스를 확장하여) 테스트가 실패하고 userRepository.findOne이 null을 반환합니다. 이 문제를 해결하는 방법에 대한 아이디어가 있습니까?
Rega

8

스프링 부트 2.1.1.RELEASE 의 마지막 버전에서는 다음 과 같이 간단합니다.

@RunWith(SpringRunner.class)
@SpringBootTest(classes = SampleApplication.class)
public class CustomerRepositoryIntegrationTest {

    @Autowired
    CustomerRepository repository;

    @Test
    public void myTest() throws Exception {

        Customer customer = new Customer();
        customer.setId(100l);
        customer.setFirstName("John");
        customer.setLastName("Wick");

        repository.save(customer);

        List<?> queryResult = repository.findByLastName("Wick");

        assertFalse(queryResult.isEmpty());
        assertNotNull(queryResult.get(0));
    }
}

완전한 코드 :

https://github.com/jrichardsz/spring-boot-templates/blob/master/003-hql-database-with-integration-test/src/test/java/test/CustomerRepositoryIntegrationTest.java


3
이것은 다소 불완전한 '예'입니다. "통합"테스트는 프로덕션 코드와 동일한 구성을 사용하여 빌드 할 수 없습니다. 즉. 아무것도 아닙니다.
Martin Mucha

죄송합니다. 이 오류 때문에 저를 채찍질 할 것입니다. 다시 한번 시도하십시오!
JRichardsz

이것은 2.0.0.RELEASESpring Boot 에서도 작동합니다 .
Nital

이 테스트에는 임베디드 db를 사용해야합니다
TuGordoBello

7

스프링 데이터 저장소에 대한 i-test를 작성하고 싶을 때 다음과 같이 할 수 있습니다.

@RunWith(SpringRunner.class)
@DataJpaTest
@EnableJpaRepositories(basePackageClasses = WebBookingRepository.class)
@EntityScan(basePackageClasses = WebBooking.class)
public class WebBookingRepositoryIntegrationTest {

    @Autowired
    private WebBookingRepository repository;

    @Test
    public void testSaveAndFindAll() {
        WebBooking webBooking = new WebBooking();
        webBooking.setUuid("some uuid");
        webBooking.setItems(Arrays.asList(new WebBookingItem()));
        repository.save(webBooking);

        Iterable<WebBooking> findAll = repository.findAll();

        assertThat(findAll).hasSize(1);
        webBooking.setId(1L);
        assertThat(findAll).containsOnly(webBooking);
    }
}

이 예제를 따르려면 다음과 같은 종속성을 사용해야합니다.

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.4.197</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.assertj</groupId>
    <artifactId>assertj-core</artifactId>
    <version>3.9.1</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

5

나는 이런 식으로 이것을 해결했다.

    @RunWith(SpringRunner.class)
    @EnableJpaRepositories(basePackages={"com.path.repositories"})
    @EntityScan(basePackages={"com.model"})
    @TestPropertySource("classpath:application.properties")
    @ContextConfiguration(classes = {ApiTestConfig.class,SaveActionsServiceImpl.class})
    public class SaveCriticalProcedureTest {

        @Autowired
        private SaveActionsService saveActionsService;
        .......
        .......
}

4

JUnit5를 사용하면 @DataJpaTest테스트가 다음과 같이 표시됩니다 (kotlin 코드).

@DataJpaTest
@ExtendWith(value = [SpringExtension::class])
class ActivityJpaTest {

    @Autowired
    lateinit var entityManager: TestEntityManager

    @Autowired
    lateinit var myEntityRepository: MyEntityRepository

    @Test
    fun shouldSaveEntity() {
        // when
        val savedEntity = myEntityRepository.save(MyEntity(1, "test")

        // then 
        Assertions.assertNotNull(entityManager.find(MyEntity::class.java, savedEntity.id))
    }
}

당신은 사용할 수 있습니다 TestEntityManager에서 org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager유효성을 엔티티 상태로 위해 패키지.


엔티티 bean에 대한 ID를 생성하는 것이 항상 더 좋은 봄입니다.
Arundev

Java의 경우 두 번째 줄은 다음과 같습니다. @ExtendWith (value = SpringExtension.class)
AdilOoze
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.