使用Mockito和BeanPostProcessors在Spring注入测试双打

我非常确定,如果您曾经使用过Spring并且熟悉单元测试,那么您会遇到与您不想修改的Spring应用程序上下文中注入模拟/间谍(测试双打)有关的问题。 本文介绍了一种使用Spring组件解决此问题的方法。

项目结构


让我们从项目结构开始:
项目结构 像往常一样提出问题,我试图显示一个非常简单的项目结构。 如果我像我们在项目中那样扩大问题的范围,我将要展示的方法可能会显示出更多的好处:

  • 我们有数十个接口和实现自动连接到列表
  • 我们希望基于现有的Spring应用程序上下文执行一些功能测试
  • 我们想要验证对于某些输入条件,某些特定的实现将执行其方法
  • 我们想存根数据库访问。

在这个例子中,我们有一个PlayerService ,它使用PlayerWebService获取一个Player 。 我们有一个applicationContext,它仅定义用于自动装配的包:

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"><context:component-scan base-package="com.blogspot.toomuchcoding"/></beans>

然后我们有一个非常简单的模型:

播放器

package com.blogspot.toomuchcoding.model;import java.math.BigDecimal;/*** User: mgrzejszczak* Date: 08.08.13* Time: 14:38*/
public final class Player {private final String playerName;private final BigDecimal playerValue;public Player(final String playerName, final BigDecimal playerValue) {this.playerName = playerName;this.playerValue = playerValue;}public String getPlayerName() {return playerName;}public BigDecimal getPlayerValue() {return playerValue;}
}

PlayerService的实现,该实现使用PlayerWebService检索有关Player数据:

PlayerServiceImpl.java

package com.blogspot.toomuchcoding.service;import com.blogspot.toomuchcoding.model.Player;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;/*** User: mgrzejszczak* Date: 08.06.13* Time: 19:02*/
@Service
public class PlayerServiceImpl implements PlayerService {private static final Logger LOGGER = LoggerFactory.getLogger(PlayerServiceImpl.class);@Autowiredprivate PlayerWebService playerWebService;@Overridepublic Player getPlayerByName(String playerName) {LOGGER.debug(String.format("Logging the player web service name [%s]", playerWebService.getWebServiceName()));return playerWebService.getPlayerByName(playerName);}public PlayerWebService getPlayerWebService() {return playerWebService;}public void setPlayerWebService(PlayerWebService playerWebService) {this.playerWebService = playerWebService;}
}

作为数据提供者的PlayerWebService的实现(在这种情况下,我们正在模拟等待响应的时间):

PlayerWebServiceImpl.java

package com.blogspot.toomuchcoding.service;import com.blogspot.toomuchcoding.model.Player;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;import java.math.BigDecimal;/*** User: mgrzejszczak* Date: 08.08.13* Time: 14:48*/
@Service
public class PlayerWebServiceImpl implements PlayerWebService {private static final Logger LOGGER = LoggerFactory.getLogger(PlayerWebServiceImpl.class);public static final String WEB_SERVICE_NAME = "SuperPlayerWebService";public static final String SAMPLE_PLAYER_VALUE = "1000";@Overridepublic String getWebServiceName() {return WEB_SERVICE_NAME;}@Overridepublic Player getPlayerByName(String name) {try {LOGGER.debug("Simulating awaiting time for a response from a web service");Thread.sleep(5000);} catch (InterruptedException e) {LOGGER.error(String.format("[%s] occurred while trying to make the thread sleep", e));}return new Player(name, new BigDecimal(SAMPLE_PLAYER_VALUE));}
}

也许项目的结构和方法不是您见过的最出色的方法之一,但我想让问题的表达保持简单;)

问题

那么到底是什么问题呢? 让我们假设我们希望自动连接的PlayerWebServiceImpl是可以验证的间谍。 而且,您不想实际更改applicationContext.xml任何内容,而是想要使用Spring上下文的当前版本。

使用模拟程序更容易,因为您可以在XML文件中定义(使用Mockito工厂方法),将bean作为模拟程序来覆盖原始实现,如下所示:

<bean id="playerWebServiceImpl" class="org.mockito.Mockito" factory-method="mock"><constructor-arg value="com.blogspot.toomuchcoding.service.PlayerWebServiceImpl"/></bean>

那间谍呢? 因为要创建间谍,您需要给定类型的现有对象,因此问题更加严重。 在我们的示例中,我们进行了一些自动装配,因此我们必须首先创建一个PlayerWebService类型的spring bean(Spring必须连接其所有依赖项),然后将其包装在Mockito.spy(...) ,然后是否必须将其连接到其他地方…变得非常复杂,不是吗?

解决方案

您可以看到问题并不是那么容易解决的。 解决该问题的一种简单方法是使用本机Spring机制– BeanPostProcessors。 您可以查看有关如何为指定类型创建Spring BeanPostProcessor的文章-在本示例中将使用它。

让我们从检查测试类开始:

PlayerServiceImplTest.java

package com.blogspot.toomuchcoding.service;import com.blogspot.toomuchcoding.model.Player;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import java.math.BigDecimal;import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.BDDMockito.doReturn;
import static org.mockito.Mockito.verify;/*** User: mgrzejszczak* Date: 08.06.13* Time: 19:26*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:testApplicationContext.xml")
public class PlayerServiceImplTest {public static final String PLAYER_NAME = "Lewandowski";public static final BigDecimal PLAYER_VALUE = new BigDecimal("35000000");@AutowiredPlayerWebService playerWebServiceSpy;@AutowiredPlayerService objectUnderTest;@Testpublic void shouldReturnAPlayerFromPlayerWebService(){//givenPlayer referencePlayer = new Player(PLAYER_NAME, PLAYER_VALUE);doReturn(referencePlayer).when(playerWebServiceSpy).getPlayerByName(PLAYER_NAME);//whenPlayer player = objectUnderTest.getPlayerByName(PLAYER_NAME);//thenassertThat(player, is(referencePlayer));verify(playerWebServiceSpy).getWebServiceName();assertThat(playerWebServiceSpy.getWebServiceName(), is(PlayerWebServiceImpl.WEB_SERVICE_NAME));}}

在此测试中,我们希望模拟从PlayerWebService检索Player (假设正常情况下它将尝试向外界发送请求,并且我们不希望这种情况发生),并测试PlayerService返回了我们在方法存根中提供的Player ,以及我们想对Spy进行验证,以确认方法getWebServiceName()已执行并且其返回值定义得非常精确。 换句话说,我们想对方法getPlayerByName(...)进行存根,并希望通过检查getWebServiceName()方法来对间谍进行验证。

让我们检查一下测试上下文:

testApplicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><import resource="applicationContext.xml"/><bean class="com.blogspot.postprocessor.PlayerWebServicePostProcessor" />
</beans>

测试上下文非常小,因为它会导入当前的applicationContext.xml并创建一个Bean,这是此示例中的关键功能– BeanPostProcessor

PlayerWebServicePostProcessor.java

package com.blogspot.postprocessor;import com.blogspot.toomuchcoding.processor.AbstractBeanPostProcessor;
import com.blogspot.toomuchcoding.service.PlayerWebService;import static org.mockito.Mockito.spy;/*** User: mgrzejszczak* Date: 07.05.13* Time: 11:30*/
public class PlayerWebServicePostProcessor extends AbstractBeanPostProcessor<PlayerWebService> {public PlayerWebServicePostProcessor() {super(PlayerWebService.class);}@Overridepublic PlayerWebService doBefore(PlayerWebService bean) {return spy(bean);}@Overridepublic PlayerWebService doAfter(PlayerWebService bean) {return bean;}
}

该类扩展了实现BeanPostProcessor接口的AbstractBeanPostProcessor 。 这个类背后的逻辑是注册类为其中一个想要之前任一初始化(执行某些动作postProcessBeforeInitialization )或豆(初始化之后postProcessAfterInitialization )。 我的帖子中很好地解释了AbstractBeanPostProcessor
Spring BeanPostProcessor用于指定的类型,但是有一点点变化–在我的旧文章中,抽象允许我们对Bean执行一些操作,而不能在Bean上返回包装器或代理。

如您在初始化之前使用Mockito.spy(...) PlayerWebServicePostProcessor ,我们正在使用Mockito.spy(...)方法创建一个Spy。 通过这种方式,我们在给定类型的Bean的初始化上创建了一个工厂钩子-就这么简单。 对于实现PlayerWebService接口的所有类,将执行此方法。

其他可能性

在检查该问题的当前解决方案时,我遇到了Jakub Janczak的Springockito库 。

我还没有使用过它,所以我不知道与此库相关的生产问题(如果有的话;)),但看起来真的很直观,很好– Jakub! 尽管如此,您仍然依赖于外部库,而在此示例中,我展示了如何使用Spring处理问题。

摘要

在这篇文章中,我展示了如何

  • 使用XML Spring配置为现有bean创建模拟
  • 创建一个BeanPostProcessor实现,该实现对给定类的bean执行逻辑
  • 对于给定的bean类,返回Spy(您也可以返回Mock)

现在,让我们看一下我的方法的优点和缺点:

优点

  • 您使用Spring本机机制为您的bean创建测试双打
  • 您不需要添加任何其他外部依赖项
  • 如果您使用AbstractBeanPostProcessor ,则只需执行很少的更改

缺点

  • 您必须熟悉内部Spring体系结构(它使用BeanPostProcessors)–但这是不利吗? ;)–实际上,如果您使用AbstractBeanPostProcessor ,则不必熟悉它–您只需提供类类型和初始化前后要发生的操作即可。
  • 它不像Springockito库中的注释那样直观

资料来源

源代码可从TooMuchCoding BitBucket存储库和TooMuchCoding Github存储库中获得 。

参考:在博客上 使用我们的JCG合作伙伴 Marcin Grzejszczak的Mockito和BeanPostProcessors在Spring注入测试双打, 用于编码成瘾者博客。

翻译自: https://www.javacodegeeks.com/2013/08/injecting-test-doubles-in-spring-using-mockito-and-beanpostprocessors.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/367274.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

二叉搜索时与双向链表python_JZ26-二叉搜索树与双向链表

1、中序遍历&#xff0c;当前结点&#xff0c;以及左侧排好序的双向链表&#xff0c;再调整当前结点的指针指向最前结点/* struct TreeNode {int val;struct TreeNode *left;struct TreeNode *right;TreeNode(int x) :val(x), left(NULL), right(NULL) {} };*/ class Solution …

html右缩进怎么设置,WPS中怎么设置右缩进两个字符?

回答&#xff1a;打开我们的Word文档&#xff0c;调整好我们的文字内容&#xff0c;然后全选我们的文字内容&#xff0c;注意要分段时按下键盘上的回车键另起一行。请点击输入图片描述接着&#xff0c;我们点击顶部菜单栏的“开始”菜单&#xff0c;在开始菜单下面的子菜单中找…

VS2013专业版+QT5.6.3+qt-vs-addin-1.2.5环境搭建

一、工具资料&#xff1a; 1.vs2013专业版地址&#xff1a;http://download.csdn.net/download/u010368556/10238145 2.qt各版本地址&#xff1a;http://download.qt.io/archive/qt/ 3.qt-vs插件地址&#xff1a;http://download.qt.io/archive/vsaddin/ 二、环境搭建过程&…

使用ActiveMQ和HornetQ通过WebSocket通过STOMP轻松进行消息传递

消息传递是用于构建不同级别的分布式软件系统的极其强大的工具。 通常&#xff0c;至少在Java生态系统中&#xff0c;客户端&#xff08;前端&#xff09;从不直接与消息代理&#xff08;或交换&#xff09;进行交互&#xff0c;而是通过调用服务器端&#xff08;后端&#xff…

【laravel】【转发】laravel 导入导出excel文档

1、简介 Laravel Excel 在 Laravel 5 中集成 PHPOffice 套件中的 PHPExcel &#xff0c;从而方便我们以优雅的、富有表现力的代码实现Excel/CSV文件的导入和 导出 。 该项目的GitHub地址是&#xff1a; https://github.com/Maatwebsite/Laravel-Excel 。 本文我们将在Laravel中…

你真的了解css像素嘛?

在日常开发中&#xff0c;px一定是大家接触过最多的css单位&#xff0c;但是你真的了解px嘛&#xff1f;1px在屏幕中到底是多大呢&#xff1f;另外不知道大家有没有过下面这些疑惑: 为什么一个元素在pc上和移动端的物理尺寸不一样&#xff0c;但是两者的视觉效果上却差不多呢&…

mysql for mac中文_mysql for Mac 下创建数据表中文显示为?的解决方法

在我的绝版Mac mini下安装了mysql 5.7版本&#xff0c;实例中&#xff0c;在通过load data 导入数据时发现表中的中文显示为 &#xff1f;通过百度&#xff0c;发现多个版本的解决方法&#xff0c;将其中一个成功解决的方法贴上来&#xff1a;大多方法都是这样&#xff1a;需要…

计算机科学计算方面分为,计算机方面的专业分为哪些类?【资讯与计算科学】和【电脑科学与技术专业】有什么不同?...

计算机方面的专业分为哪些类&#xff1f;【资讯与计算科学】和【电脑科学与技术专业】有什么不同&#xff1f;以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;计算机方面的专业分为哪些类&am…

Java异常处理教程(包含示例和最佳实践)

异常是可能在程序执行期间发生的错误事件&#xff0c;它会破坏其正常流程。 Java提供了一种健壮且面向对象的方式来处理异常情况&#xff0c;称为Java异常处理 。 我们将在本教程中研究以下主题。 Java异常处理概述 异常处理关键字 异常层次 有用的异常方法 Java 7自动资源…

GMTC 大前端时代前端监控的最佳实践

本文来自阿里云前端监控团队&#xff0c;转载请注明出处本文为2018年6月21日&#xff0c;在北京举办的GMTC(全球大前端技术大会)&#xff0c;下午性能与监控专场&#xff0c;由阿里云前端监控团队前端技术专家彭伟春带来的演讲稿&#xff0c;现场反馈效果非常好&#xff0c;地上…

Alpha阶段敏捷冲刺②

1.提供当天站立式会议照片一张 每个人的工作 &#xff08;有work item 的ID&#xff09;&#xff0c;并将其记录在码云项目管理中&#xff1a; 昨天已完成的工作。 购买云服务器 注册账号 界面布局初步规划 今天计划完成的工作。 界面雏形设计 数据库初步设计 完成后端框架初步…

透明地持久保存并从数据库中检索加密的数据

自从我在这里发表上一个帖子以来已经有两个多月了&#xff0c;但是今年六月和七月非常忙碌而密集。 首先&#xff0c; Confitura的组织&#xff08;欧洲最大的Java开发人员免费会议&#xff09;参加了我所有的免费晚会&#xff0c;然后在相当紧张的住院期间&#xff0c;我们的第…

[译] 2017 年比较 Angular、React、Vue 三剑客

原文地址&#xff1a;Angular vs. React vs. Vue: A 2017 comparison原文作者&#xff1a;Jens Neuhaus译文出自&#xff1a;掘金翻译计划本文永久链接&#xff1a;https://github.com/xitu/gold-miner/blob/master/TODO/angular-vs-react-vs-vue-a-2017-comparison.md译者&…

centos 离线安装mysql_CentOS6离线安装mysql-5.7.25

1.mysql-5.7.25-1.el6.x86_64.rpm-bundle.tar下载百度云资源提取码&#xff1a;ej1y2.把下载的mysql安装包上传到Centos上解压mysql&#xff0c;我这是在Windows上解压的上传到Centos上&#xff0c;我在Centos上解压mysql不知道为什么少了rpm -ivh mysql-community-common-5.7.…

Linux自动化之Cobbler补鞋匠安装

cobbler介绍&#xff1a; 快速网络安装linux操作系统的服务&#xff0c;支持众多的Linux发行版&#xff1a;Red Hat、 Fedora、CentOS、Debian、Ubuntu和SuSE&#xff0c;也可以支持网络安装windows PXE的二次封装&#xff0c;将多种安装参数封装到一个菜单 Python编…

如何以10倍速加速Apache Xalan的XPath处理器

一段时间以来&#xff0c; Apache Xalan中存在一个令人尴尬的错误&#xff0c;该错误是XALANJ-2540 。 此错误的后果是Xalan每次XPath表达式求值将内部SPI配置文件加载数千次 &#xff0c;可以很容易地进行如下测量&#xff1a; 这个&#xff1a; Element e (Element)documen…

使用ant design Pro开发项目的小结

一、关于上手。 1. 关于ant design Pro的介绍&#xff0c;自己看官网&#xff0c;大致上可以理解为ant design&#xff08;组件库&#xff09; ant design Pro &#xff08;完整的项目&#xff09; dva&#xff08;路由 数据流管理&#xff09;的组合拳。总之这个Pro是一个已经…

Activiti中的高级脚本:自定义配置注入

脚本任务可能是Activiti代码库中“最古老的”类之一&#xff0c;但我认为它仍然未被许多人使用。 &#xff08;可以理解的&#xff1f;&#xff09;缺点当然是性能&#xff08;解释还是编译&#xff09;&#xff0c;并且从IDE角度来看支持较少。 但是&#xff0c;好处&#xf…

帆软决策报表嵌入html,在决策报表中使用网页框控件

假设决策报表里有一个网页框控件&#xff0c;控件名为rHIframe0&#xff1b;同时有三个按钮控件&#xff0c;分别给按钮控件添加下面的点击事件&#xff1a;3.1 setValue(String)设置并刷新网页框控件的地址(保留原参数)1)模板路径var Widget this.options.form.getWidgetByNa…

【前端轶事】Chrome 小恐龙背后的故事

本文转自 FEPulse 公众号&#xff08;微信搜索 FEPulse&#xff0c;精选国内外最新前端资讯&#xff0c;为你把握前端脉搏&#xff09;。 如果你是 Chrome 用户&#xff0c;一定对那萌萌哒的小恐龙不陌生&#xff0c;每当互联网连接断开时&#xff0c;你便能看到那只小恐龙&am…