如何模拟Spring bean(版本2)

大约一年前,我写了一篇博客文章如何模拟Spring Bean 。 所描述的模式对生产代码几乎没有侵入性。 正如读者Colin在评论中正确指出的那样,基于@Profile注释的间谍/模拟Spring bean是更好的选择。 这篇博客文章将描述这种技术。 我在工作中以及副项目中都成功使用了这种方法。

请注意,在您的应用程序中普遍出现的嘲笑通常被视为设计气味。

介绍生产代码

首先,我们需要测试代码以演示模拟。 我们将使用以下简单的类:

@Repository
public class AddressDao {public String readAddress(String userName) {return "3 Dark Corner";}
}@Service
public class AddressService {private AddressDao addressDao;@Autowiredpublic AddressService(AddressDao addressDao) {this.addressDao = addressDao;}public String getAddressForUser(String userName){return addressDao.readAddress(userName);}
}@Service
public class UserService {private AddressService addressService;@Autowiredpublic UserService(AddressService addressService) {this.addressService = addressService;}public String getUserDetails(String userName){String address = addressService.getAddressForUser(userName);return String.format("User %s, %s", userName, address);}
}

当然,这段代码没有多大意义,但对于演示如何模拟Spring bean来说将是一个很好的选择。 AddressDao只是返回字符串,因此模拟了从某些数据源的读取。 它自动连接到AddressService 。 该bean被自动连接到UserService ,后者用于构造带有用户名和地址的字符串。

请注意,我们将构造函数注入用作字段注入被认为是不好的做法。 如果要为应用程序强制执行构造函数注入,Oliver Gierke(Spring生态系统开发人员和Spring Data负责人)最近创建了一个非常不错的项目Ninjector 。

扫描所有这些bean的配置是相当标准的Spring Boot主类:

@SpringBootApplication
public class SimpleApplication {public static void main(String[] args) {SpringApplication.run(SimpleApplication.class, args);}
}

模拟Spring Bean(无AOP)

让我们在模拟AddressDao地方测试AddressService类。 我们可以创建通过Spring”这个模拟@Profiles@Primary注释是这样的:

@Profile("AddressService-test")
@Configuration
public class AddressDaoTestConfiguration {@Bean@Primarypublic AddressDao addressDao() {return Mockito.mock(AddressDao.class);}
}

仅当Spring概要文件AddressService-test处于活动状态时,才会应用此测试配置。 应用时,它将注册AddressDao类型的bean,该类型是Mockito创建的模拟实例。 @Primary注释告诉Spring在有人自动装配AddressDao bean时使用此实例,而不是实际实例。

测试类使用的是JUnit框架:

@ActiveProfiles("AddressService-test")
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(SimpleApplication.class)
public class AddressServiceITest {@Autowired private AddressService addressService;@Autowiredprivate AddressDao addressDao;@Testpublic void testGetAddressForUser() {// GIVENMockito.when(addressDao.readAddress("john")).thenReturn("5 Bright Corner");// WHEN String actualAddress = addressService.getAddressForUser("john");// THEN   Assert.assertEquals("5 Bright Corner", actualAddress);}
}

我们激活配置文件AddressService-test以启用AddressDao AddressService-test 。 Spring集成测试需要使用@RunWith注释,而@SpringApplicationConfiguration定义将使用哪种Spring配置来构造测试环境。 在测试之前,我们自动连接被测试的AddressService实例和AddressDao模拟。

如果您使用的是Mockito,则随后的测试方法应明确。 在GIVEN阶段,我们将所需的行为记录到模拟实例中。 在WHEN阶段,我们执行测试代码,在THEN阶段,我们验证测试代码是否返回了我们期望的值。

监视Spring Bean(无AOP)

对于间谍示例,将在AddressService实例上进行间谍:

@Profile("UserService-test")
@Configuration
public class AddressServiceTestConfiguration {@Bean@Primarypublic AddressService addressServiceSpy(AddressService addressService) {return Mockito.spy(addressService);}
}

仅当配置文件UserService-test处于活动状态时,才会对此组件配置进行Spring扫描。 它定义了AddressService类型的主bean。 @Primary告诉Spring使用该实例,以防在Spring上下文中存在两个这种类型的bean。 在构造此bean的过程中,我们从Spring上下文自动装配了AddressService现有实例,并使用Mockito的间谍功能。 我们正在注册的bean有效地将所有调用委托给原始实例,但是Mockito间谍程序使我们可以验证所侦查实例的交互。

我们将以这种方式测试UserService行为:

@ActiveProfiles("UserService-test")
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(SimpleApplication.class)
public class UserServiceITest {@Autowiredprivate UserService userService;@Autowiredprivate AddressService addressService;@Testpublic void testGetUserDetails() {// GIVEN - Spring scanned by SimpleApplication class// WHENString actualUserDetails = userService.getUserDetails("john");// THENAssert.assertEquals("User john, 3 Dark Corner", actualUserDetails);Mockito.verify(addressService).getAddressForUser("john");}
}

为了进行测试,我们激活了UserService-test配置文件,因此将应用我们的间谍配置。 我们自动装配UserService这是在测试和AddressService ,目前正在通过窥探的Mockito。

我们不需要为在GIVEN阶段进行测试准备任何行为。 W HEN相被测明显执行代码。 在THEN阶段,我们验证测试代码是否返回了我们期望的值,以及是否使用正确的参数执行了addressService调用。

Mockito和Spring AOP的问题

假设现在我们要使用Spring AOP模块来处理一些跨领域的问题。 例如,以这种方式记录对Spring Bean的调用:

package net.lkrnac.blog.testing.mockbeanv2.aoptesting;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;import lombok.extern.slf4j.Slf4j;@Aspect
@Component
@Slf4j
@Profile("aop") //only for example purposes
public class AddressLogger {@Before("execution(* net.lkrnac.blog.testing.mockbeanv2.beans.*.*(..))")public void logAddressCall(JoinPoint jp){log.info("Executing method {}", jp.getSignature());}
}

在从包net.lkrnac.blog.testing.mockbeanv2调用Spring bean之前,将应用此AOP方面。 它使用Lombok的注释@Slf4j记录调用方法的签名。 注意,仅当定义了aop概要文件时才创建此bean。 我们正在使用此配置文件将AOP和非AOP测试示例分开。 在实际的应用程序中,您不想使用此类配置文件。

我们还需要为我们的应用程序启用AspectJ,因此以下所有示例都将使用此Spring Boot主类:

@SpringBootApplication
@EnableAspectJAutoProxy
public class AopApplication {public static void main(String[] args) {SpringApplication.run(AopApplication.class, args);}
}

AOP构造由@EnableAspectJAutoProxy启用。

但是,如果我们将Mockito与Spring AOP结合进行模拟,则此类AOP构造可能会出现问题。 这是因为两者都使用CGLIB代理真实实例,并且当Mockito代理包装到Spring代理中时,我们会遇到类型不匹配的问题。 这些可以通过使用ScopedProxyMode.TARGET_CLASS配置bean的作用域来ScopedProxyMode.TARGET_CLASS ,但是Mockito的verify ()调用仍然会因NotAMockException失败。 如果我们为UserServiceITest启用aop配置文件,则可以看到此类问题。

由Spring AOP代理的模拟Spring Bean

为了克服这些问题,我们将模拟包装到这个Spring bean中:

package net.lkrnac.blog.testing.mockbeanv2.aoptesting;import org.mockito.Mockito;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Repository;import lombok.Getter;
import net.lkrnac.blog.testing.mockbeanv2.beans.AddressDao;@Primary
@Repository
@Profile("AddressService-aop-mock-test")
public class AddressDaoMock extends AddressDao{@Getterprivate AddressDao mockDelegate = Mockito.mock(AddressDao.class);public String readAddress(String userName) {return mockDelegate.readAddress(userName);}
}

@Primary注释可确保在注入过程中,此bean优先于实际的AddressDao bean。 为了确保仅将其应用于特定测试,我们为此bean定义了配置文件AddressService-aop-mock-test 。 它继承了AddressDao类,因此可以完全替代该类型。

为了伪造行为,我们定义了AddressDao类型的模拟实例,该实例通过由Lombok的@Getter批注定义的getter @Getter 。 我们还实现了readAddress()方法,该方法有望在测试期间被调用。 此方法仅将调用委派给模拟实例。

使用该模拟程序的测试如下所示:

@ActiveProfiles({"AddressService-aop-mock-test", "aop"})
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(AopApplication.class)
public class AddressServiceAopMockITest {@Autowiredprivate AddressService addressService; @Autowiredprivate AddressDao addressDao;@Testpublic void testGetAddressForUser() {// GIVENAddressDaoMock addressDaoMock = (AddressDaoMock) addressDao;Mockito.when(addressDaoMock.getMockDelegate().readAddress("john")).thenReturn("5 Bright Corner");// WHEN String actualAddress = addressService.getAddressForUser("john");// THEN  Assert.assertEquals("5 Bright Corner", actualAddress);}
}

在测试中,我们定义AddressService-aop-mock-test配置文件以激活AddressDaoMock并定义aop配置文件以激活AddressLogger AOP方面。 为了进行测试,我们自动装配了bean addressService及其伪造的依赖项addressDao 。 我们知道, addressDao将是AddressDaoMock类型的,因为此bean被标记为@Primary 。 因此,我们可以将其mockDelegate转换mockDelegate行为记录到mockDelegate

当我们调用测试方法时,应使用记录的行为,因为我们希望测试方法使用AddressDao依赖项。

监视Spring AOP代理的Spring bean

类似的模式可用于监视实际实现。 这就是我们的间谍的样子:

package net.lkrnac.blog.testing.mockbeanv2.aoptesting;import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;import lombok.Getter;
import net.lkrnac.blog.testing.mockbeanv2.beans.AddressDao;
import net.lkrnac.blog.testing.mockbeanv2.beans.AddressService;@Primary
@Service
@Profile("UserService-aop-test")
public class AddressServiceSpy extends AddressService{@Getterprivate AddressService spyDelegate;@Autowiredpublic AddressServiceSpy(AddressDao addressDao) {super(null);spyDelegate = Mockito.spy(new AddressService(addressDao));}public String getAddressForUser(String userName){return spyDelegate.getAddressForUser(userName);}
}

如我们所见,该间谍与AddressDaoMock非常相似。 但是在这种情况下,真正的bean使用构造函数注入来自动装配其依赖关系。 因此,我们需要定义非默认构造函数,并且还要进行构造函数注入。 但是我们不会将注入的依赖项传递给父构造函数。

为了启用对真实对象的监视,我们将构造具有所有依赖项的新实例,将其包装到Mockito间谍实例中,并将其存储到spyDelegate属性中。 我们期望在测试期间调用方法getAddressForUser() ,因此我们将此调用委托给spyDelegate 。 可以在测试中通过由Lombok的@Getter批注定义的getter访问此属性。

测试本身如下所示:

@ActiveProfiles({"UserService-aop-test", "aop"})
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(AopApplication.class)
public class UserServiceAopITest {@Autowiredprivate UserService userService;@Autowiredprivate AddressService addressService;@Testpublic void testGetUserDetails() {// GIVENAddressServiceSpy addressServiceSpy = (AddressServiceSpy) addressService;// WHENString actualUserDetails = userService.getUserDetails("john");// THEN Assert.assertEquals("User john, 3 Dark Corner", actualUserDetails);Mockito.verify(addressServiceSpy.getSpyDelegate()).getAddressForUser("john");}
}

这是非常简单的。 配置文件UserService-aop-test确保可以扫描AddressServiceSpy 。 配置文件aopAddressLogger方面确保相同。 当我们自动测试对象UserService及其依赖项AddressService ,我们知道可以将其spyDelegateAddressServiceSpy并在调用测试方法后验证其spyDelegate属性的调用。

由Spring AOP代理的假Spring Bean

显然,将调用委派给Mockito模拟或间谍会使测试复杂化。 如果我们仅需要伪造逻辑,那么这些模式通常会被大刀阔斧。 在这种情况下,我们可以使用这些伪造品:

@Primary
@Repository
@Profile("AddressService-aop-fake-test")
public class AddressDaoFake extends AddressDao{public String readAddress(String userName) {return userName + "'s address";}
}

并将其用于这种方式的测试:

@ActiveProfiles({"AddressService-aop-fake-test", "aop"})
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(AopApplication.class)
public class AddressServiceAopFakeITest {@Autowiredprivate AddressService addressService; @Testpublic void testGetAddressForUser() {// GIVEN - Spring context// WHEN String actualAddress = addressService.getAddressForUser("john");// THEN  Assert.assertEquals("john's address", actualAddress);}
}

我认为这个测试不需要解释。

  • 这些示例的源代码托管在Github上。

翻译自: https://www.javacodegeeks.com/2016/01/mock-spring-bean-version-2.html

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

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

相关文章

eclipse和jdk的版本问题,比如printf()出错

1.右键项目选中properties 2.Java Builder Path 》》》Libraries》》》选中JRE Syste Library[jre7]然后remove 3.Add Library》》》JRE Syetem Library》》》NEXT》》》Alternate JRE:选中最新的jie版本 4.Finish》》》OK! 5.再在右键项目选中properties 》》》Ja…

java自定义一个timeout,Timeout操作符 RxJava 学习笔记二十一

timeout用于检测在给定时间内observables没有及时响应。如果指定的时间量没有发出任何项目,则超时会使observables失败并出现TimeoutException。我们将从debounce的示例中重用我们的observable来演示超时。输出:只要值不超过200ms,就会输出。…

用xshell ssh连接测试服务器时候出的问题

问题还原:用ssh连接测试服务器 给我结结实实报了个错 FBIwarning: ----------------------------------------------------------------------------------------------------------------------------以上 问题还原 解决过程:百度 博客地址http://blog.…

maven集成spring_Maven集成测试和Spring Restful Services

maven集成spring介绍 我的原始博客通过一个非常简单的示例展示了如何分离Maven单元和集成测试。 http://johndobie.blogspot.com/2011/06/seperating-maven-unit-integration-tests.html此后,许多人要求我提供比最初使用的示例更实际的示例。 这篇文章显示了如何在实…

WebService.asmx架设后,显示调用按钮的方法

我们在VS本地调试WebService的时候,页面是可以调试的,如下图 但是在发布以后,通过浏览器访问服务器上的地址,是不能调试你的程序的,就变成了这个样子: 可以通过修改服务器上的web.config达到远程调试的效果…

复习者-Java EE 7概览

随着红帽JBoss企业应用平台7(EAP 7)的出现,甚至最近刚刚通过Java EE 7认证的WebLogic服务器,我认为现在应该为您重新介绍Java Enterprise Edition 7的时候了功能,并指向更多资源。 Java EE 7 –开发人员的生产力&…

php教程知识点归纳,PHP知识点小结

PHP知识点总结?1.?DIRECTORY_SEPARATOR??? ? ? 因为在window上分隔符是"\"而在linux上分隔符是"/" ?使用这个符号就可以避免这个问题。??2.?realpath??? ? ?返回文件所在的绝对路径。??3. 在php中 0null 是成立的。?? ???4.file_ge…

40:数组中只出现一次的数字

/*** 面试题40:数组中只出现一次的数字* 一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。*/ public class _40_num_one_times {public static void main(String[] args){Solution40 solution40 new Solu…

按窗口文件php代码,在Windows命令行窗口中输入并运行PHP代码片段(不需要php文件)的方法...

有时候只是简单的为了测试某个php函数的效果,以前总是需要建一个php文件,复制这个文件的路径,再通过web访问或者用php命令执行这个php文件。一直想要怎么才能不用创建文件,才能直接执行PHP代码片段呢?终于找到了方法&a…

MySQL 汉字转拼音

一 . fristPinyin : 此函数是将一个中文字符串的第一个汉字转成拼音字母 (例如:"中国人"->Z) CREATE FUNCTION fristPinyin(P_NAME VARCHAR(255)) RETURNS varchar(255) CHARSET utf8 BEGINDECLARE V_RETURN VARCHAR…

java中npe问题,【Java 8】巧用Optional之优雅规避NPE问题

避之不及的 NullPointerExceptionNPE : NullPointerException空指针异常是最常见的Java异常之一,抛出NPE错误不是用户操作的错误,而是开发人员的错误,应该被避免,那么只能在每个方法中加入非空检查,阅读性和维护性都比…

adf435编程_动态ADF列车:以编程方式添加列车停靠点

adf435编程我将展示如何以编程方式“即时”将火车停靠站添加到ADF火车中。 在我的用例中,我有一些票务预订应用程序。 它具有训练模型的有限任务流。 在火车的第一站,用户输入乘客数量,在随后的站点,他们输入一些乘客信息。 必须根…

JAVA消息服务JMS规范及原理详解

一、简介 JMS即Java消息服务(Java Message Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。Ja…

php mk的支持扩展,Linux部署Redis及PHP-redis扩展

Redis是一个key-value存储系统,属于我们常说的NoSQL。它遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。它常用在缓存、队列、Pub/Sub、计数统计、排行榜、投票与排序等场景。本文介绍如何在CentOS7上安装…

定制Hamcrest Matchers

本文是我们名为“ 用Mockito进行测试 ”的学院课程的一部分。 在本课程中,您将深入了解Mockito的魔力。 您将了解有关“模拟”,“间谍”和“部分模拟”的信息,以及它们相应的Stubbing行为。 您还将看到使用测试双打和对象匹配器进行验证的过…

Fiddle用于移动端抓包

一、什么情况下可以用到 1、调查参考其他移动端网站的抓包,他们传输方式。如微信上京东的智能机器人的包、移动端的请求接口格式、如何实现的效果等。 2、调试本地移动端页面的测试页面效果是否有问题。如:页面的样式、js、结构等。 二、操作(http) a、安装猎豹免费…

php中得到复选框的数据的代码,表单复选框向PHP传输数据的代码

表单复选框向PHP传输数据的代码表单复选框就是checkbox1、checkbox的应用复制代码 代码如下:2、由于我传输的是在php循环中产生的数组&#xff0c;因此value也要设成变量&#xff1a;for($i0;$i<10;$i){?>">]" value"要传输的内容">]" v…

ubuntu14.04不能安全卸载移动硬盘

ubuntu14.04不能安全卸载移动硬盘 sudo udisksctl unmount -b /dev/sdb1 udisksctl power-off -b /dev/sdb hdparm -Y /dev/sdb 转载于:https://www.cnblogs.com/fedorayang/p/6563552.html

Kioptrix-2

环境搭建 这个靶场环境稍微有点麻烦&#xff0c;首次打开的时候&#xff0c;需要将靶机从VM中移除&#xff08;注意是 从VM里面移除&#xff09;&#xff0c;然后利用nodpad等工具打开vmx文件&#xff0c;然后两步&#xff1a; 所有以“ethernet0”开头的条目并保存更改。然后…

php 递归到空如何处理,php递归调用删除数组空值元素的方法

这篇文章主要介绍了php递归调用删除数组空值元素的方法,涉及php递归调用操作数组的相关技巧,非常具有实用价值,需要的朋友可以参考下本文实例讲述了php递归调用删除数组空值元素的方法。分享给大家供大家参考。具体如下&#xff1a;该函数可以删除数组里的所有空值元素&#xf…