首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >问答首页 >如何测试Spring数据存储库?

如何测试Spring数据存储库?
EN

Stack Overflow用户
提问于 2014-05-02 11:54:48
回答 11查看 267K关注 0票数 181

我希望在Spring的帮助下创建一个存储库(比如UserRepository)。我对spring数据(但不是spring)很陌生,我使用这个教程。我选择的处理数据库的技术是JPA2.1和Hibernate。问题是,对于如何为这样的存储库编写单元测试,我一无所知。

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

  • 首先,如何将EntityManager的模拟注入到UserRepository接口的不存在实现中?Spring数据将根据这个接口生成一个实现: 公共接口UserRepository扩展CrudRepository {} 但是,我不知道如何强制它使用EntityManager模拟和其他模拟--如果我自己编写了实现,我可能会有一个用于EntityManager的setter方法,允许我在单元测试中使用模拟。(至于实际的数据库连接,我有一个用@Configuration@EnableJpaRepositories注释的@Configuration类,它以编程方式为DataSourceEntityManagerFactoryEntityManager等定义beans -但是存储库应该是测试友好的,并且允许覆盖这些东西)。
  • 第二,我应该测试交互吗?我很难弄清楚EntityManagerQuery的哪些方法应该被调用(类似于verify(entityManager).createNamedQuery(anyString()).getResultList();),因为编写实现的不是我。
  • 第三,我应该首先对Spring生成的方法进行单元测试吗?据我所知,第三方库代码不应该进行单元测试--只有开发人员自己编写的代码才应该进行单元测试。但是如果是这样的话,它仍然会把第一个问题带回场景:比如说,我为我的存储库提供了一些自定义方法,我将为此编写实现,如何将EntityManagerQuery的模拟注入到最终生成的存储库中?

注意:我将测试-使用集成和单元测试来驱动我的存储库。对于我的集成测试,我使用的是内存中的HSQL数据库,显然我没有使用数据库进行单元测试。

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

更新:今天,我继续对模拟注入进行实验--我创建了一个静态内部类以允许模拟注入。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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());
}

}

但是,运行此测试将给出以下堆栈跟踪:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
EN

回答 11

Stack Overflow用户

回答已采纳

发布于 2014-05-03 01:16:34

tl;dr

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

详细信息

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

  • 我们为派生查询创建和缓存CriteriaQuery实例,以确保查询方法不包含任何类型的输入。这需要使用标准API以及meta.model。
  • 我们通过请求EntityManager为这些查询创建一个Query实例来验证手动定义的查询(这有效地触发了查询语法验证)。
  • 我们检查Metamodel中关于处理域类型的元数据,准备is-新检查等等。

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

考虑一下,您没有为存储库编写代码,因此没有必要编写任何_unit_tests。根本没有必要,因为您可以依靠我们的测试库来捕获基本的bug(如果您仍然遇到一个bug,可以随意地引发一个票证)。但是,肯定需要集成测试来测试持久化层的两个方面,因为它们是与您的域相关的方面:

  • 实体映射
  • 查询语义(每次引导尝试都会验证语法)。

集成测试

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

测试自定义实现

存储库的自定义实现部分是以某种方式写成,它们不需要了解Spring。它们是普通的Spring,会被注入EntityManager。当然,你可能想试着嘲笑它的交互,但老实说,对我们来说,对JPA进行单元测试并不是一种令人愉快的体验,而且它与相当多的间接(EntityManager,->,CriteriaBuilderCriteriaQuery等)一起工作。这样,您就可以得到模拟、返回的模拟等等。

票数 153
EN

Stack Overflow用户

发布于 2017-04-25 04:28:58

对于Spring + Spring数据,它变得非常容易:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@RunWith(SpringRunner.class)
@DataJpaTest
public class MyRepositoryTest {

    @Autowired
    MyRepository subject;

    @Test
    public void myTest() throws Exception {
        subject.save(new MyEntity());
    }
}

@heez的解决方案提供了完整的上下文,这只会带来JPA+Transaction工作所需的内容。注意,上面的解决方案将弹出内存测试数据库,因为可以在类路径上找到一个数据库。

票数 68
EN

Stack Overflow用户

发布于 2015-02-20 22:46:14

这可能来得太晚了,但我已经为此目的写了一些东西。我的库将为您模拟基本的crud存储库方法,并解释查询方法的大部分功能。您必须为您自己的本机查询注入功能,但其余的功能都是为您完成的。

看一看:

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

更新

这是现在的Maven中心和相当好的状态。

票数 28
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/23435937

复制
相关文章
MySQL信号量等待日志阅读
lock_word的初始值为X_LOCK_DECR,每次添加x锁时原子递减X_LOCK_DECR,每次添加S锁时原子递减1。每次添加SX锁时递减X_LOCK_HALF_DECR。如果读写锁支持递归写,那么第一个递归写锁加锁成功时,lock_word 依然原子递减X_LOCK_DECR,而后续的递归写锁加锁成功是,lock_word 只是原子递减1。
donghy
2022/09/17
1.1K0
有界数组模板类(类模板)
编写有界数组模板BoundArray(即检查对数组元素下标引用并在下标越界时终止程序的执行),能够存储各种类型的数据。要求实现对数组进行排序的方法sort,及对数组进行查找的方法search。(不能直接调用C++自带的排序或查找函数)
叶茂林
2023/07/30
1620
做网站收益如何?能满足生活保障吗?
看到前两天有一位网友在泪雪博客留言问到子凡我做网站收益如何?能满足生活保障吗?仔细一想好像这个问题应该是所有做网站的人都会思考的一个问题,那么下面子凡就根据自己的亲身经历和感受来回答一下这个问题,顺便整理成文发布出来。
张子凡
2022/11/02
1.3K0
做网站收益如何?能满足生活保障吗?
现在的工程结构能满足你的需求吗?
创建单模块工程相信很多人都手到擒来。但如果项目很大,功能很多,你还能继续使用单模块工程吗?项目过大,结构肯定也越来越复杂这时候如果你继续使用单模块工程,进展就会遇到各种问题。同时维护起来也是很麻烦的事情。这个时候你就可以考虑将功能细化,使用多模块工程来替代单模块了。
田维常
2019/09/12
9950
现在的工程结构能满足你的需求吗?
SaaS企业要满足客户的定制开发需求吗?
本文作者 吴昊:腾讯SaaS加速器导师、纷享销客天使投资人、前执行总裁,具有20年企业信息化和6年SaaS营销团队创新经验。 前天和一个做SaaS的团队交流,他们处在我说的《SaaS创业路线图》中的“产品打磨”阶段 —— 产品已经做出来,也找到了少量试用客户。 这时候遇到一个大家常见的问题:目标客户提出的需求超越了我们初定的需求边界,要不要做定制开发?这个问题,不少创业公司都会遇到。每家都有难念的经,具体情况各不相同,今天我为大家写几条原则吧。 第一,明确自己是做产品的,还是做项目的。做项目的公司
腾讯SaaS加速器
2020/06/09
1.1K0
信号量--System V信号量 与 Posix信号量
信号量是一种计数器,用来控制对多个进程/线程共享的资源进行访问。常和锁一同使用。 在某个进程/线程正在对某个资源进行访问时,信号量可以阻止另一个进程/线程去打扰。 生产者和消费者模型是信号量的典型使用。
看、未来
2020/08/26
1.7K0
聊聊有界上下文
在这篇文章中,我将分享我对有界上下文的看法。有界上下文是什么意思?为什么需要有界上下文?
双愚
2018/07/09
2K0
聊聊有界上下文
基于数组的有界阻塞队列 —— ArrayBlockingQueue
" 在阅读完和 AQS 相关的锁以及同步辅助器之后,来一起阅读 JUC 下的和队列相关的源码。先从第一个开始:ArrayBlockingQueue。 "
程序员小航
2020/11/23
9100
基于数组的有界阻塞队列 —— ArrayBlockingQueue
java并发编程(五)
阻塞队列:在某些情况下,会挂起线程,一旦条件满足,被挂起的线程会自动唤醒。而阻塞队列无需关心什么时候阻塞,什么时候唤醒。
疯狂的KK
2020/03/25
3010
brpc小课堂:有界队列BoundedQueue
brpc实现了一个“有界队列”的类模板BoundedQueue。先说一下什么是有界队列。 所谓有界队列表示的就是一个队列其中的容量是有限的(固定的),不能动态扩容的队列。这种听起来没有vector那种自动扩容能力的容器,主要还是全面为了性能考虑的。一般也是用作生产者和消费者模式,当队列容量已满的时候,一般就表示超过了这个队列的最大吞吐能力,故而拒绝加入新的任务。
果冻虾仁
2021/12/30
7190
brpc小课堂:有界队列BoundedQueue
信号量
1.初始化信号量 函数原型:int sem_init(sem_t* sem,int pshared,unsigned int value) 参数: sem:指定是哪一个信号量 pshared:指定信号量的类型,值为0表示是在当前进程使用的局部信号量,否则该信号量就可以在多个进程中共享。 value:指定信号量的初始值,可以理解为最多由多少个线程可以访问共享资源。 返回值:
lexingsen
2022/02/25
4740
信号量
Hystrix内部提供了两种模式执行逻辑:信号量和线程池。默认情况下,Hystrix使用线程池模式。那什么是信号量呢?
只喝牛奶的杀手
2019/08/26
6630
Python中线程同步与线程锁「建议收藏」
概念 * 线程同步,线程间协同,通过某种技术,让一个线程访问某些数据时,其他线程不能访问这些数据,直到该线程完 成对数据的操作。
全栈程序员站长
2022/08/31
7320
Python中线程同步与线程锁「建议收藏」
学会等待
摘要总结:学会等待是一种能力——控制自己的能力。当内心特别热衷于某一个东西的时候,提醒一下自己很重要。
雷大亨
2017/12/29
1.4K0
分布式锁中的王者方案 - Redisson
如果你之前是在用 Redis 的话,那使用 Redisson 的话将会事半功倍,Redisson 提供了使用 Redis的最简单和最便捷的方法。
macrozheng
2021/07/02
1.6K0
分布式锁中的王者方案 - Redisson
临界区 互斥量 事件 信号量_互斥信号量与同步信号量
1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。 2、互斥量:为协调共同对一个共享资源的单独访问而设计的。 3、信号量:为控制一个具有有限数量用户资源而设计。 4、事 件:用来通知线程有一些事件已发生,从而启动后继任务的开始。
全栈程序员站长
2022/09/23
8310
基于链表的有界阻塞队列 —— LinkedBlockingQueue
" 上一节看了基于数据的有界阻塞队列 ArrayBlockingQueue 的源码,通过阅读源码了解到在 ArrayBlockingQueue 中入队列和出队列操作都是用了 ReentrantLock 来保证线程安全。下面咱们看另一种有界阻塞队列:LinkedBlockingQueue。 "
程序员小航
2020/11/23
5920
基于链表的有界阻塞队列 —— LinkedBlockingQueue
分布式锁中的王者方案 - Redisson
上篇讲解了如何用 Redis 实现分布式锁的五种方案,但我们还是有更优的王者方案,就是用 Redisson。
悟空聊架构
2022/05/13
1.3K0
分布式锁中的王者方案 - Redisson
Selenium4+Python3系列(六) - Selenium的三种等待,强制等待、隐式等待、显式等待
用一句通俗易懂的话就是:等待元素已被加载完全之后,再去定位该元素,就不会出现定位失败的报错了。
软件测试君
2022/12/05
3K0
Selenium4+Python3系列(六) - Selenium的三种等待,强制等待、隐式等待、显式等待
点击加载更多

相似问题

带有Web组件的Dart服务器

10

在web服务器中运行dart

41

Dart: web web/src

13

节点web服务器崩溃

20

在dart中使用parseInt时服务器崩溃?

11
添加站长 进交流群

领取专属 10元无门槛券

AI混元助手 在线答疑

扫码加入开发者社群
关注 腾讯云开发者公众号

洞察 腾讯核心技术

剖析业界实践案例

扫码关注腾讯云开发者公众号
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文