当前位置: 首页 > 知识库问答 >
问题:

如何测试Spring数据存储库?

黄和怡
2023-03-14

我想要一个在Spring数据的帮助下创建的存储库(例如userrepository)。我不熟悉spring-data(但不熟悉spring),我使用本教程。我选择的处理数据库的技术是JPA2.1和Hibernate。问题是我不知道如何为这样的存储库编写单元测试。

让我们以create()方法为例。由于我正在进行测试--首先,我应该为它编写一个单元测试--这就是我遇到三个问题的地方:

>

  • 首先,如何将EntityManager的模拟注入到不存在的UserRepository接口实现中?Spring Data将基于此接口生成一个实现:

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

    但是,我不知道如何强制它使用EntityManager模拟和其他模拟--如果我自己编写了实现,我可能会有一个用于EntityManager的setter方法,允许我使用模拟进行单元测试。(至于实际的数据库连接,我有一个jpaconfiguration类,用@configuration@enablejparepositories注释,它以编程方式定义了DataSourceEntityManagerFactoryEntityManager等的bean-但是存储库应该是测试友好的,并允许重写这些东西)。

    第二,我应该测试交互吗?我很难弄清楚应该调用什么entityManagerQuery的方法(类似于Verify(entityManager).CreateNamedQuery(anyString()).GetResultList();),因为编写实现的不是我。

    第三,我是否应该首先对Spring数据生成的方法进行单元测试?正如我所知,第三方库代码不应该是单元测试的--只有开发人员自己编写的代码才应该是单元测试的。但是如果这是真的,那么第一个问题仍然会回到现场:比方说,我有几个自定义方法用于我的存储库,我将为其编写实现,我如何将EntityManagerQuery的模拟注入到最终生成的存储库中?

    注意:我将使用集成和单元测试来测试我的存储库。对于我的集成测试,我使用的是一个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());
    }
    
    }
    

    但是,运行此测试会给我以下StackTrace:

    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个答案

    濮阳君浩
    2023-03-14

    简而言之,没有办法对Spring数据JPA存储库进行合理的单元测试,原因很简单:模拟我们为引导存储库而调用的JPA API的所有部分会很麻烦。单元测试在这里没有太大的意义,因为您通常不会自己编写任何实现代码(请参见下面关于自定义实现的段落),因此集成测试是最合理的方法。

    我们做了相当多的前期验证和设置,以确保您只能引导一个应用程序,没有无效的派生查询等。

    • 我们为派生查询创建并缓存criteriaquery实例,以确保查询方法不包含任何错别字。这需要使用标准API和meta.model。
    • 我们通过要求EntityManager为这些查询创建query实例来验证手动定义的查询(这将有效地触发查询语法验证)。
    • 我们检查元模型中有关处理的域类型的元数据,以准备is-new检查等。

    您可能会在手写存储库中延迟的所有内容,这些内容可能会导致应用程序在运行时中断(由于无效查询等)。

    如果您考虑一下,您没有为存储库编写代码,因此不需要编写任何UnitTests。完全没有必要,因为您可以依赖我们的测试库来捕捉基本的bug(如果您仍然碰巧遇到一个bug,请随意提出罚单)。但是,肯定需要集成测试来测试持久性层的两个方面,因为它们是与您的域相关的方面:

    • 实体映射
    • 查询语义(无论如何在每次引导尝试时验证语法)。

    这通常是通过使用内存中的数据库和测试用例来完成的,测试用例通常通过测试上下文框架引导SpringApplicationContext(正如您已经做的那样),预填充数据库(通过EntityManager或repo插入对象实例,或者通过一个纯SQL文件),然后执行查询方法来验证它们的结果。

    存储库的自定义实现部分是以他们不必了解Spring Data JPA的方式编写的。它们是注入EntityManager的普通Spring bean。当然,您可能想尝试模拟与JPA的交互,但老实说,对我们来说,单元测试JPA并不是一个太愉快的体验,而且它与相当多的间接操作(EntityManager->CriteriaBuilderCriteriaQuery等)一起工作,因此最终会出现模拟返回模拟等等。

     类似资料:
    • 问题内容: 我已经开发了一个Spring Data仓库,扩展了接口有一个方法: 结果由Spring缓存抽象(由)支持缓存。 我的问题是,我想要写一个集成测试(针对HSQLDB)断言结果被从数据库第一次检索,并从缓存中的第二次。 我最初想到模拟jpa基础结构(实体管理器等),并以某种方式断言第二次不调用实体管理器,但似乎太难/麻烦了)。 然后有人可以提供有关如何测试带有注释的Spring Data

    • 我最初想嘲弄一下jpa基础结构(实体管理器等),并以某种方式断言实体管理器不是第二次调用的,但它似乎太难/太累赘了(参见https://stackoverflow.com/A/23442457/536299)。 那么,有人能提供建议,如何测试用注释的Spring数据存储库方法的缓存行为吗?

    • } 然后我有我的服务课,看起来像这样 然后是我的存储库:

    • 我是新的弹簧靴,做了我的第一个实体。如何测试存储库?我使用的是本地MySQL数据库。那么第一步就是嘲弄数据库? 而我的存储库只是扩展了Jparepository。 我得到的错误是java.lang.IllegalStateException:未能加载ApplicationContext。还有一点:无法用嵌入式数据库替换DataSource进行测试。如果您想要一个嵌入式数据库,请在类路径上放置一个受

    • 又不想工作,我不知道是怎么回事。 日志包含以下消息: 将项目放在github https://github.com/romanych2021/testjpaspring上