[unit-testing] 스프링 데이터 리포지토리를 테스트하는 방법?

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



답변

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등) 때문에 모의를 반환하는 모의로 끝납니다.


답변

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 + 트랜잭션이 작동하는 데 필요한 것만 가져옵니다. 위의 솔루션은 클래스 패스에서 찾을 수있는 메모리 테스트 데이터베이스를 가져옵니다.


답변

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

구경하다:

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

최신 정보

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


답변

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"));
  }
}

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


답변

스프링 부트 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


답변

스프링 데이터 저장소에 대한 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>


답변

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

    @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;
        .......
        .......
}