web-dev-qa-db-ja.com

Spring Dataのリポジトリをテストするにはどうすればいいですか?

Spring Dataの助けを借りて作成したリポジトリ(例えばUserRepository)が欲しいのですが。私はspring-dataに慣れていない(しかしspringにはなっていない)そして私はこれを使っている チュートリアル 。データベースを扱うための私のテクノロジーの選択はJPA 2.1とHibernateです。問題は、そのようなリポジトリに対して単体テストを書く方法について私が何も知らないことです。

例えばcreate()メソッドを取りましょう。私がテストファーストで作業しているとき、私はそれのためにユニットテストを書くことになっています - そしてそれは私が3つの問題にぶつかるところです:

  • まず、どのようにして既存のEntityManagerインターフェースの実装にUserRepositoryのモックを注入するのですか? Spring Dataは、このインタフェースに基づいて実装を生成します。

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

    しかし、私はEntityManagerモックや他のモックを使うことを強制する方法がわからない - 私が実装を自分で書いたなら、おそらく私はEntityManagerのためのセッターメソッドを持っているでしょう。 (実際のデータベース接続性に関しては、私はJpaConfigurationDataSourceEntityManagerFactoryなどのためにBeanをプログラム的に定義する@Configurationおよび@EnableJpaRepositoriesでアノテートされたEntityManagerクラスを持っています - しかし、リポジトリはテストしやすくこれらをオーバーライドできるはずです)。

  • 次に、相互作用をテストする必要がありますか?実装を書いているのは私ではないので、EntityManagerQueryのどのメソッドが呼び出されることになっているのかを判断するのは困難です(verify(entityManager).createNamedQuery(anyString()).getResultList();に似ています)。

  • 第三に、最初にSpring-Dataが生成したメソッドをユニットテストすることになっていますか?私が知っているように、サードパーティのライブラリコードはユニットテストされることになっていません - 開発者自身が書くコードだけがユニットテストされることになっています。しかし、それが真実ならば、それでも最初の質問をシーンに持ち帰ります。たとえば、私は自分のリポジトリにカスタムメソッドをいくつか持っています。そのために実装を書く予定です、どうやってEntityManagerQueryのモックを最後に入れますか? 、生成されたリポジトリ?

注:統合テストと単体テストの両方を使用してリポジトリをテスト駆動します。私の統合テストでは、HSQLのインメモリデータベースを使用しています。単体テストにはデータベースを使用していません。

そしておそらく4番目の質問ですが、統合テストで正しいオブジェクトグラフの作成とオブジェクトグラフの取得をテストするのは正しいのでしょうか(たとえば、Hibernateで定義された複雑なオブジェクトグラフがあります)。

更新日:今日、私は模擬注入を実験し続けました - 私は模擬注入を可能にするために静的な内部クラスを作成しました。

@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
113
user1797032

tl; dr

簡単に言うと、Spring Data JPAリポジトリを合理的に単体テストする方法はありません。起動するJPA APIのすべての部分をモックするのは面倒です。統合テストが最も合理的なアプローチであるように、通常自分自身で実装コードを書いていないので(カスタム実装については下の段落を参照)、単体テストはここではあまり意味がありません。

詳細

無効な派生クエリなどを持たないアプリのみをブートストラップできるようにするために、かなりの数の事前検証とセットアップを行います。

  • クエリメソッドにタイプミスが含まれていないことを確認するために、派生クエリのCriteriaQueryインスタンスを作成してキャッシュします。これには、meta.modelと同様にCriteria APIを使用する必要があります。
  • 手動で定義されたクエリを検証するために、EntityManagerにそれらのインスタンスのQueryインスタンスを作成するよう依頼します(これにより、クエリ構文の検証が効果的にトリガされます)。
  • Metamodelを調べて、新規チェックなどを準備するために処理されたドメインタイプに関するメタデータを調べます。

おそらく手書きのリポジトリで延期されるすべてのものは、アプリケーションが実行時に壊れる可能性があります(無効なクエリなどのため)。

考えてみれば、リポジトリ用に書くコードがないので、単体テストを書く必要はありません。基本的なバグをキャッチするために私たちのテストベースに頼ることができるので、単に必要はありません(あなたがまだ1つに遭遇した場合、 ticket を上げてください。ただし、永続化レイヤの2つの側面をテストするための統合テストは、ドメインに関連する側面であるため、間違いなく必要です。

  • エンティティマッピング
  • クエリセマンティクス(とにかく各ブートストラップの試行で構文が検証されます)。

統合テスト

これは通常、インメモリデータベースを使用し、(既に行っているように)通常はテストコンテキストフレームワークを通じてSpringのApplicationContextをブートストラップし、(EntityManagerまたはrepoを通じてオブジェクトインスタンスを挿入することによって)プレーンなSQLファイル)を作成し、クエリメソッドを実行してそれらの結果を確認します。

カスタム実装のテスト

リポジトリのカスタム実装部分は ある意味で であり、Spring Data JPAについて知る必要はありません。それらはEntityManagerが注入されたプレーンなSpring beanです。もちろん、JPAの単体テストはあまりにも楽しい経験ではありませんでした(EntityManager - > CriteriaBuilderCriteriaQueryなど)。あなたはモックを返すモックなどで終わるように。

96
Oliver Drotbohm

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 +トランザクションが機能するために必要なものだけをもたらします。上記の解決策は、クラスパスで見つけることができるとすれば、インメモリテストデータベースを立ち上げることに注意してください。

33
Markus T

これは少し遅すぎるかもしれませんが、私はこのまさにその目的のために何かを書きました。私のライブラリはあなたのための基本的なcrudリポジトリメソッドを模擬するだけでなく、あなたのクエリメソッドの機能の大部分を解釈します。あなたはあなた自身のネイティブクエリのために機能性を注入しなければならないでしょうが、残りはあなたのために行われます。

見てください。

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

UPDATE

これは現在、Mavenの中心部にあり、非常に良い状態です。

20
Milad Naseri

Spring Bootを使っているのであれば、単にApplicationContextを読み込むために@SpringBootTestを使うことができます。これにより、スプリングデータリポジトリに自動配線することができます。春特有のアノテーションが拾われるように@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"));
  }
}

あなたは彼らの docs で春のブートでテストについてもっと読むことができます。

13
heez

私はこのようにしてこれを解決しました -

    @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
Ajay

Spring Data Repository用の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>
4
Philipp Wirth

春のブートの最後のバージョン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
JRichardsz

JUnit5と@DataJpaTestテストで(コトリンコード)のようになります:

@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))
    }
}

エンティティの状態を検証するためにorg.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManagerパッケージからTestEntityManagerを使用することができます。

2
Przemek Nowak