如何testingSpring数据仓库?

我想要一个在Spring Data的帮助下创build的仓库(比如UserRepository )。 我是新的春季数据(但不是spring),我用这个教程 。 我select处理数据库的技术是JPA 2.1和Hibernate。 问题是,我对如何为这样的存储库编写unit testing一无所知。

让我们以create()方法为例。 由于我正在testing第一,所以我应该写一个unit testing – 这就是我遇到的三个问题:

  • 首先,如何将一个EntityManager的模拟注入UserRepository接口的不存在的实现中? Spring Data会根据这个接口生成一个实现:

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

    然而,我不知道如何强制它使用EntityManager模拟和其他模拟 – 如果我自己写了实现,我可能会有一个EntityManager的setter方法,允许我使用我的模拟进行unit testing。 (至于实际的数据库连接,我有一个JpaConfiguration类,用@Configuration@EnableJpaRepositories注释,它在编程上为DataSourceEntityManagerFactoryEntityManager等定义了bean。但是,存储库应该是testing友好的并允许重写这些东西)。

  • 其次,我应该testing一下互动吗? 我很难弄清楚应该调用什么EntityManagerQuery方法(类似于verify(entityManager).createNamedQuery(anyString()).getResultList(); ),因为不是我正在写执行。

  • 第三,我应该首先对Spring数据生成的方法进行unit testing吗? 据我所知,第三方库代码不应该被unit testing – 只有开发者编写的代码应该是unit testing的。 但是,如果这是真的,它仍然会把第一个问题带回到现场:比如,我有一些自定义方法用于存储库,为此我将编写实现,如何将EntityManagerQueryEntityManager注入最终,生成存储库?

注意:我将使用集成testing和unit testing来testing我的版本库。 对于我的集成testing,我正在使用HSQL内存数据库,显然我没有使用数据库进行unit testing。

也许是第四个问题,在集成testing中testing正确的对象图创build和对象图检索是否正确(比如,我有一个用Hibernate定义的复杂对象图)?

更新:今天我继续尝试模拟注入 – 我创build了一个静态内部类允许模拟注入。

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

但是,运行这个testing给了我以下的堆栈跟踪:

 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;博士

为了简短起见 – 没有办法合理地unit testingSpring Data JPA存储库,原因很简单:模拟所有我们调用的引导存储库的JPA API部分是很麻烦的。 unit testing在这里没有多less意义,因为你通常不是自己编写任何实现代码(参见下面关于自定义实现的段落),所以集成testing是最合理的方法。

细节

我们做了很多前期validation和设置,以确保您只能引导没有无效派生查询的应用程序。

  • 我们为派生查询创build并cachingCriteriaQuery实例,以确保查询方法不包含任何拼写错误。 这需要使用Criteria API以及meta.model。
  • 我们通过要求EntityManager为那些创build一个Query实例来validation手动定义的查询(这有效地触发了查询语法validation)。
  • 我们检查Metamodel是否有关处理的域types的元数据以准备新的检查等。

所有的东西,你可能会推迟在一个手写的存储库,可能会导致应用程序在运行时中断(由于无效查询等)。

如果你仔细想想,你的代码库没有编写代码,所以不需要编写任何单元testing。 根本没有必要,因为你可以依靠我们的testing基地来抓住基本的错误(如果你碰巧碰到一个,随时提高票 )。 但是,肯定需要集成testing来testing持久层的两个方面,因为它们是与您的域相关的方面:

  • 实体映射
  • 查询语义(无论如何,语法在每个引导尝试中都被validation)。

集成testing

这通常是通过使用内存数据库和testing用例来实现的,这些数据库和testing用例通常通过testing上下文框架来引导Spring ApplicationContext (就像您已经做的那样),预先填充数据库(通过EntityManager或repo插入对象实例,或通过一个普通的SQL文件),然后执行查询方法来validation它们的结果。

testing自定义实现

存储库的自定义实现部分以不需要了解Spring Data JPA 的方式编写 。 它们是创build一个EntityManager注入的纯Spring bean。 您当然可以试图嘲笑与之交互,但说实话,unit testingJPA对我们来说并不是太愉快的经历,它也适用于相当多的间接( EntityManager – > CriteriaBuilderCriteriaQuery等),所以你最终嘲笑返回嘲笑等。

这可能来得太晚,但是我为此写了一些东西。 我的图书馆将为您嘲笑基本的粗俗储存库方法,并解释查询方法的大部分function。 你将不得不为自己的本地查询注入function,但其余的都是为你完成的。

看一看:

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

UPDATE

现在在Maven中央,状态很好。

有了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的解决scheme提供了完整的上下文,这只是提出了JPA +事务工作所需的东西。 请注意,上面的解决scheme将带来一个内存testing数据库,因为可以在类path中find一个。

如果你使用的是Spring Boot,你可以简单地使用@SpringBootTest来加载你的ApplicationContext (这是你的@SpringBootTest你的东西)。 这使您可以在您的spring-data存储库中自动装入。 一定要添加@RunWith(SpringRunner.class)以便获取特定于Spring的注释:

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

你可以在他们的文档中阅读更多关于春季启动的testing。