UserRepository我想要一个在 Spring Data 的帮助下创建的存储库(比如, )。我是 spring-data 的新手(但不是 spring),我使用本教程。我选择的处理数据库的技术是 JPA 2.1 和 Hibernate。问题是我对如何为这样的存储库编写单元测试一无所知。
UserRepository
我们以create()方法为例。当我首先进行测试时,我应该为它编写一个单元测试——这就是我遇到三个问题的地方:
create()
EntityManager
public interface UserRepository extends CrudRepository<User, Long> {}
但是,我不知道如何强制它使用EntityManager模拟和其他模拟 - 如果我自己编写了实现,我可能会有一个 setter 方法EntityManager,允许我使用模拟进行单元测试。(至于实际的数据库连接,我有一个用andJpaConfiguration注释的类,它以编程方式为,等定义 bean - 但存储库应该是测试友好的,并允许覆盖这些东西)。@Configuration``@EnableJpaRepositories``DataSource``EntityManagerFactory``EntityManager
JpaConfiguration
@Configuration``@EnableJpaRepositories``DataSource``EntityManagerFactory``EntityManager
其次,我应该测试交互吗?我很难弄清楚应该调用什么方法EntityManager和Query应该调用什么方法(类似于 that verify(entityManager).createNamedQuery(anyString()).getResultList();),因为编写实现的不是我。
Query
verify(entityManager).createNamedQuery(anyString()).getResultList();
第三,我应该首先对 Spring-Data 生成的方法进行单元测试吗?据我所知,第三方库代码不应该进行单元测试——只有开发人员自己编写的代码才应该进行单元测试。但如果那是真的,它仍然把第一个问题带回到现场:比如说,我有几个自定义方法用于我的存储库,我将为此编写实现,我如何将我的模拟注入EntityManager到Query最终生成的存储库?
注意:我将使用集成和单元测试来测试我的存储 库 。对于我的集成测试,我使用的是 HSQL 内存数据库,显然我没有使用数据库进行单元测试。
可能是第四个问题,在集成测试中测试正确的对象图创建和对象图检索是否正确(例如,我有一个用 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
Metamodel
您可能会推迟到手写存储库中的所有内容,这可能会导致应用程序在运行时中断(由于无效查询等)。
如果您考虑一下,您无需为存储库编写代码,因此无需编写任何 单元 测试。根本没有必要,因为您可以依靠我们的测试库来捕获基本错误(如果您仍然碰巧遇到一个错误,请随时提出票)。但是,肯定需要集成测试来测试持久层的两个方面,因为它们是与您的域相关的方面:
这通常是通过使用内存数据库和测试用例来ApplicationContext完成的,这些测试用例通常通过测试上下文框架(正如您已经做的那样)引导 Spring,预填充数据库(通过EntityManager或 repo 或通过普通SQL 文件),然后执行查询方法来验证它们的结果。
ApplicationContext
存储库的自定义实现部分以他们不必了解 Spring Data JPA的方式编写。它们是被EntityManager注入的普通 Spring bean。您当然可能想尝试模拟与它的交互,但老实说,对 JPA 进行单元测试对我们来说并不是一个太愉快的体验,而且它适用于很多间接(EntityManager->CriteriaBuilder等CriteriaQuery)所以你最终得到模拟返回模拟等等。
CriteriaBuilder