我非常确定,如果您曾经使用过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

使用Mockito和BeanPostProcessors在Spring注入测试双打相关推荐

  1. Spring MVC测试框架

    原文链接:http://jinnianshilongnian.iteye.com/blog/2004660 Spring MVC测试框架详解--服务端测试 博客分类: springmvc杂谈 spri ...

  2. Spring MVC测试框架入门–第1部分

    最新推出的主要Spring框架是Spring MVC测试框架,Spring Guys声称它是"一流的JUnit支持,可通过流畅的API测试客户端和服务器端Spring MVC代码" ...

  3. 14.6 Spring MVC 测试框架(翻译)

    14.6 Spring MVC 测试框架(每天翻译一点点) Spring MVC测试框架对 Spring MVC 代码提供一流的测试支持 ,它拥有一个 fluent API ,可以和JUnit, Te ...

  4. Spring MVC测试框架详解——服务端测试

    随着RESTful Web Service的流行,测试对外的Service是否满足期望也变的必要的.从Spring 3.2开始Spring了Spring Web测试框架,如果版本低于3.2,请使用sp ...

  5. spring boot测试_测试Spring Boot有条件的合理方式

    spring boot测试 如果您或多或少有经验的Spring Boot用户,那么很幸运,在某些时候您可能需要遇到必须有条件地注入特定bean或配置的情况 . 它的机制是很好理解的 ,但有时这样的测试 ...

  6. spring注入私有字段_Spring字段依赖注入示例

    spring注入私有字段 了解如何编写Spring Field Injection示例 . 字段注入是Spring框架 依赖注入的一种 . 在本教程中,我们将编写几个类,并看一看现场注入工程. 有关S ...

  7. 在Spring Boot测试中使用Testcontainer进行数据库集成测试

    在此博客文章中,我想演示如何在Spring Boot测试中集成Testcontainer以便与数据库一起运行集成测试. 我没有使用Testcontainers的Spring Boot模块. 如何与他们 ...

  8. Spring MVC测试框架入门–第2部分

    这个迷你系列的第一个博客介绍了Spring MVC测试框架,并展示了其在单元测试Spring MVC Controller类中作为控制器而不是POJO进行单元测试的用途. 现在是时候讨论使用框架进行集 ...

  9. 你写的代码扩展性高吗?快试试用Spring注入方式来解耦代码!

    点击关注公众号,实用技术文章及时了解 来源:blog.csdn.net/qq_38050259/article/ details/113414419 目的:对比传统方式和 Spring注入方式创建对象 ...

最新文章

  1. 利用Servlet生成动态验证码
  2. excel合并同类项数据求和
  3. crt中 新建的连接存储在哪_数字存储示波器的VPO技术
  4. 教你学习CI框架codelgniter——CI框架基本配置
  5. DX中材质不能正确显示的问题(要么黑色,要么白色)
  6. python工资这么高为什么不学-Python为什么这么火 Python岗位薪资水平如何
  7. axios 封装数据请求
  8. 敏捷开发(Agile)
  9. 搜狗新闻爬取怎么破解反爬机制呀,求指教
  10. 《人性的弱点》——戴尔·卡耐基
  11. colorkey唇釉是否安全_colorkey唇釉安全吗-colorkey唇釉真假辨别
  12. 妮维雅 || 德国百年“大宝”的年轻态营销
  13. 计算机自动获取IP地址流程详解
  14. Windows Server 2008 Standard Enterprise Datacenter各个版本区别
  15. 关于无线网络的简单整理
  16. mybatisplus多源配置报错:Failed to determine a suitable driver class
  17. 2020-03-22-fNIRS技术入门文章
  18. 手机端也能免费查看CAD图纸啦!
  19. 360浏览器的兼容模式
  20. 自己构建pomelo的Cocos2d-iPhone Client

热门文章

  1. 转:在eclipse中搭建maven工程(第二种方法)
  2. latex如何使节标题居左_为使节构建控制平面的指南第3部分-特定于域的配置API...
  3. jpa 忽略bean_在WildFly上将JPA和CDI Bean与骆驼一起使用
  4. 数据结构压缩_将数据压缩到数据结构中
  5. java压缩文件读取_用Java读取/写入压缩和非压缩文件
  6. mongodb连接java_如何从Java EE无状态应用程序连接到MongoDB
  7. 雅加达EE:干净的板岩
  8. 使用Spring Boot和GraphQL构建安全的API
  9. 使用LocalDate,LocalTime和LocalDateTime
  10. 在Spring Boot中使用Vaadin的简介