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
のためのセッターメソッドを持っているでしょう。 (実際のデータベース接続性に関しては、私はJpaConfiguration
、DataSource
、EntityManagerFactory
などのためにBeanをプログラム的に定義する@Configuration
および@EnableJpaRepositories
でアノテートされた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
簡単に言うと、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
- > 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
UPDATE
これは現在、Mavenの中心部にあり、非常に良い状態です。
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 で春のブートでテストについてもっと読むことができます。
私はこのようにしてこれを解決しました -
@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;
.......
.......
}
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>
春のブートの最後のバージョン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));
}
}
完全なコード
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
を使用することができます。