Spring Dataリポジトリをテストするには?質問する

Spring Dataリポジトリをテストするには?質問する

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で注釈が付けられたクラスがあり、プログラムで、などの Bean を定義しますが、リポジトリはテスト フレンドリで、これらのオーバーライドを許可する必要があります)。@Configuration@EnableJpaRepositoriesDataSourceEntityManagerFactoryEntityManager

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

  • 3 番目に、そもそも Spring-Data が生成したメソッドを単体テストする必要があるのでしょうか? ご存知のとおり、サードパーティのライブラリ コードは単体テストの対象ではありません。開発者自身が記述したコードのみが単体テストの対象となるはずです。しかし、それが本当であれば、最初の質問が再び出てきます。たとえば、リポジトリにカスタム メソッドがいくつかあり、その実装を記述する場合、最終的に生成されたリポジトリにEntityManagerのモックをどのように挿入すればよいのでしょうか?Query

注: 私はリポジトリをテストするために両方統合テストと単体テストです。統合テストでは 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

ベストアンサー1

要約

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

詳細

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

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

手書きのリポジトリに保留すると、実行時にアプリケーションが壊れる可能性があるすべてのもの (無効なクエリなどにより)。

考えてみれば、リポジトリ用に書くコードはないので、ユニットテストは不要です。基本的なバグはテストベースで検出できるので、テストは必要ありません(それでもバグに遭遇した場合は、お気軽にご連絡ください)。チケットただし、ドメインに関連する側面であるため、永続化レイヤーの 2 つの側面をテストするための統合テストが確実に必要です。

  • エンティティマッピング
  • クエリのセマンティクス (いずれにしても、構文は各ブートストラップ試行で検証されます)。

統合テスト

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

カスタム実装のテスト

リポジトリのカスタム実装部分は書かれた方法Spring Data JPA について知る必要はありません。これらは、EntityManager注入される単純な Spring Bean です。もちろん、これとのやり取りをモック化したい場合もあるでしょうが、正直に言うと、JPA のユニット テストは私たちにとってあまり楽しい経験ではありませんでした。JPA は、非常に多くの間接参照 ( EntityManager->CriteriaBuilderなどCriteriaQuery) で動作するため、モックがモックを返すなど、結果的に不便です。

おすすめ記事