Spring Dataリポジトリをテストする方法は?


136

UserRepositorySpring Dataの助けを借りて作成されたリポジトリ(たとえば、)が必要です。私はspring-dataを初めて使用しますが(springは使用しません)、このチュートリアルを使用します。データベースを扱うための私のテクノロジーの選択は、JPA 2.1とHibernateです。問題は、そのようなリポジトリの単体テストを作成する方法について私が無知であることです。

create()メソッドを例にとってみましょう。私は最初にテストを行っているので、そのための単体テストを作成することになっています。そこで、3つの問題にぶつかります。

  • 最初に、インターフェイスのEntityManager存在しない実装にモックを注入するにはどうすればよいUserRepositoryですか?Spring Dataはこのインターフェースに基づいた実装を生成します:

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

    ただし、EntityManagerモックや他のモックを使用するように強制する方法がわかりません。自分で実装を記述した場合、おそらくのセッターメソッドがありEntityManager、モックをユニットテストに使用できます。(実際のデータベース接続に関しては、およびのJpaConfiguration注釈が付けられたクラスが@Configurationあり@EnableJpaRepositories、プログラムDataSourceによってEntityManagerFactoryEntityManagerなどのBeanを定義していますが、リポジトリはテストに適しており、これらのものをオーバーライドできる必要があります)。

  • 次に、相互作用をテストする必要がありますか?私はどのような方法を把握することは困難であるEntityManagerQuery(それに似呼ばれるようになっているverify(entityManager).createNamedQuery(anyString()).getResultList();)、それは実装を書いている人は私ではないので、。

  • 3番目に、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

回答:


118

tl; dr

簡単に言うと、単純な理由から、Spring Data JPAリポジトリを合理的に単体テストする方法はありません。リポジトリをブートストラップするために呼び出すJPA APIのすべての部分をモックするのは面倒です。統合テストが最も合理的なアプローチになるように、通常は自分で実装コードを記述しないため(カスタム実装に関する以下の段落を参照)、ユニットテストはあまり意味がありません。

細部

無効な派生クエリがないアプリのみをブートストラップできることを確認するために、多くの事前検証とセットアップを行っています。

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

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

あなたが考えてみれば、あなたがあなたのリポジトリのために書く何のコードがありませんので、任意の記述する必要はありませんユニットテストが。私たちのテストベースに依存して基本的なバグをキャッチできるため、必要はありません(まだバグが発生した場合は、遠慮なくチケットを上げてください)。ただし、永続化レイヤーの2つの側面はドメインに関連する側面であるため、それらをテストするための統合テストが確実に必要です。

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

統合テスト

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

カスタム実装のテスト

リポジトリのカスタム実装部分は、Spring Data JPAについて知る必要がないように記述されています。それらはEntityManager注入されるプレーンなスプリング豆です。もちろん、それとの相互作用を模擬したいと思うかもしれませんが、正直に言うと、JPAの単体テストはあまり快適な経験ではなく、多くの間接参照(EntityManager-> CriteriaBuilderCriteriaQueryなど)でも機能するので、最終的にモックがモックを返すことになるなど。


5
インメモリデータベース(h2など)との統合テストの小さな例へのリンクはありますか?
Wim Deblauwe、2015年

7
ここの例はHSQLDBを使用しています。H2への切り替えは、基本的にで依存関係を交換することですpom.xml
Oliver Drotbohm、2015年

3
ありがとうございます。ただし、データベースに事前入力したり、実際にデータベースをチェックしたりする例を期待していました。
Wim Deblauwe、2015年

1
「ある方法で書かれた」の背後にあるリンクが機能しなくなった。多分それを更新できますか?
Wim Deblauwe、2015年

1
それで、カスタム実装のために単体テストの代わりに統合テストを使用することを提案しますか?そして、それらのユニットテストをまったく書かないのですか?明確にするために。はいなら大丈夫です。私は理由を理解しています(すべてのものをあざけるには複雑すぎる)。私はJPAテストに慣れていないので、それを理解したいだけです。
Ruslan Stelmachenko 2016年

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 + Transactionが機能するために必要なものだけを表示します。上記の解決策は、クラスパスで見つけることができる場合、メモリ内テストデータベースを表示することに注意してください。


7
これは、統合テストではなく、ユニット OPが言及こと試験
硫黄クチャルスキ

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(これがスタックトレースで発生します)。これにより、Spring-Dataリポジトリで自動配線を行うことができます。@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

これ自体ではありませんPredicateが、s(これは私のユースケースでした)をテストしたいとします。
heez

1
私にとってリポジトリは常にnullです。何か助け?
Atul Chaudhary

これが最良の答えです。このようにして、CrudRepo、エンティティ、およびエンティティのテーブルを作成するDDLスクリプトをテストします。
MirandaVeracruzDeLaHoyaCardina 2018

私はこれとまったく同じテストを書きました。リポジトリの実装がjdbcTemplateを利用する場合、それは完全に機能します。しかし、Spring-dataの実装を変更すると(Repositoryからインターフェースを拡張することにより)、テストが失敗し、userRepository.findOneがnullを返します。これを解決する方法のアイデアはありますか?
レガ

8

Spring Boot 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

これは2.0.0.RELEASE、Spring Boot でも機能します。
Nital

このテストには埋め込みデータベースを使用する必要があります
TuGordoBello

7

Springデータリポジトリの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、from org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManagerパッケージを使用できます。


Entity BeanのIdを生成するための常により良い春。
Arundev 2018

Javaの場合、2行目は次のとおりです。@ExtendWith(value = SpringExtension.class)
AdilOoze
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.