[解決済み] Spring Dataのリポジトリをテストするには?
質問
リポジトリが欲しい(例えば
UserRepository
)をSpring Dataの助けを借りて作成しました。私はspring-dataに新しいです(しかし、春にはない)、私はこれを使用します。
チュートリアル
. データベースを扱うために私が選択した技術は、JPA 2.1とHibernateです。問題は、このようなリポジトリに対するユニットテストをどのように書くかについて、私は無知であるということです。
それでは
create()
メソッドを例にとって説明します。私はテストファーストで作業しているので、このメソッドのユニットテストを書くことになっているのですが、ここで3つの問題にぶつかりました。
-
まず、このモックに
EntityManager
の存在しない実装にUserRepository
インターフェイスですか?Spring Dataはこのインターフェイスに基づいた実装を生成することになります。public interface UserRepository extends CrudRepository<User, Long> {}
しかし、どうすれば強制的に
EntityManager
モックなどです。もし私が自分で実装を書いたとしたら、おそらくセッター・メソッドでEntityManager
このモックを使って、ユニットテストを行うことができます。(実際のデータベース接続に関しては、私はJpaConfiguration
クラスで、アノテーションは@Configuration
と@EnableJpaRepositories
のビーンをプログラム的に定義するものです。DataSource
,EntityManagerFactory
,EntityManager
などがあります。- しかし、リポジトリはテストフレンドリーであるべきで、これらのことをオーバーライドできるようにすべきです)。 -
次に、インタラクションのテストはした方がいいのでしょうか?のどのメソッドを使えばいいのか、私にはわかりません。
EntityManager
とQuery
が呼び出されることになっています(あのverify(entityManager).createNamedQuery(anyString()).getResultList();
)、実装を書いているのは私ではないので。 -
第三に、そもそも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
解決方法は?
tl;dr
簡単に説明すると、Spring Data JPAリポジトリを合理的にユニットテストする方法はありません。ユニットテストはあまり意味がありません。実装コードを自分で書くことは通常ないので(カスタム実装に関する以下の段落を参照)、統合テストが最も合理的なアプローチとなります。
詳細
無効な派生クエリなどがないアプリのみをブートストラップできるように、かなり多くの事前検証やセットアップを行っています。
-
を作成し、キャッシュします。
CriteriaQuery
インスタンスを作成し、クエリメソッドにタイポが含まれていないことを確認します。これは meta.model と同様に Criteria API で作業する必要があります。 -
手動で定義したクエリを検証するために
EntityManager
を作成しQuery
のインスタンスを作成します (これは効果的にクエリ構文検証のトリガーとなります)。 -
を検査します。
Metamodel
のメタデータを取得し、is-newチェックなどを準備する。
手書きのリポジトリでは、(無効なクエリなどにより)実行時にアプリケーションが壊れる可能性があるため、おそらく延期するようなものばかりです。
考えてみれば、リポジトリのために書くコードはないので ユニット をテストします。基本的なバグは私たちのテストベースに任せておけばよいので、その必要はありません。 チケット ). しかし、永続化レイヤーの2つの側面をテストするための統合テストは、あなたのドメインに関連する側面であるため、間違いなく必要です。
- エンティティマッピング
- クエリのセマンティクス(構文はブートストラップごとに検証されます)。
統合テスト
これは通常、インメモリデータベースと、Spring APIをブートストラップするテストケースを使用して行われます。
ApplicationContext
通常、テストコンテキストフレームワークを通じて (すでに行っているように) データベースに事前にデータを入れ、 (オブジェクトのインスタンスを
EntityManager
やレポ、あるいはプレーンなSQLファイルを介して)クエリメソッドを実行し、その結果を検証します。
カスタム実装のテスト
レポジトリのカスタム実装部分は
書き方
というのは、Spring Data JPAを知らなくてもいいようにするためです。それらは単なるSpringビーンであり
EntityManager
を注入しています。もちろん、モックを使ってインタラクションを試してみたいかもしれませんが、正直なところ、JPAのユニットテストは私たちにとってあまり楽しい経験ではありませんでした。
EntityManager
->
CriteriaBuilder
,
CriteriaQuery
など)を使って、モックがモックを返すというようなことが起こってしまいます。
関連
-
[解決済み] Spring Data JPAにおけるCrudRepositoryとJpaRepositoryのインターフェースの違いは何ですか?
-
[解決済み] プライベートメソッド、フィールド、インナークラスを持つクラスをテストするにはどうすればよいですか?
-
[解決済み] ユニットテストとインテグレーションテストの違いは何ですか?[重複あり]
-
[解決済み】ユニットテスト、インテグレーションテスト、スモークテスト、リグレッションテストとは何ですか?終了
-
[解決済み】ユニットテスト初心者、優れたテストを書くには?[クローズド]
-
[解決済み] Visual Studio 2015または2017でユニットテストが検出されない
-
[解決済み] ユニットテストはゲッターとセッターのために書くべきですか?
-
[解決済み] ファイルシステムに依存するコードの単体テスト
-
[解決済み] TDDとBDDの主な違いは何ですか?[クローズド]
-
[解決済み] 例:無効なutf8文字列?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】Assert.Fail()はバッドプラクティスとみなされるか?
-
[解決済み] MOCKITOとは何か、Junitとはどう違うか
-
[解決済み] フェイク、モッキング、スタビングの違いとは?
-
[解決済み] ユニットテストとインテグレーションテストの違いは何ですか?[重複あり]
-
[解決済み】ユニットテストと機能テストの違いは何ですか?
-
[解決済み] クロームの拡張機能をテストするには?
-
[解決済み] Visual Studio 2015または2017でユニットテストが検出されない
-
[解決済み] Unit Testsでランダムデータ?
-
[解決済み] 既存のプロダクションプロジェクトにユニットテストをうまく追加することができますか?もしそうなら、どのように、そして、それは価値があるのでしょうか?
-
[解決済み] GTestとCMakeを使った作業の始め方