spring-bean版本_如何模拟Spring bean(版本2)

spring-bean版本

大约一年前,我写了一篇博客文章如何模拟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

spring-bean版本

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

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

相关文章

四步创建TCP客户端

//四步简历TCP服务端 #include<iostream> #include<Windows.h> using namespace std;int main() {//初始化ws2_32.dllWORD ver MAKEWORD(2, 2);WSADATA wsaData;WSAStartup(ver, &wsaData);//对应WSACleanup的调用//1.建立一个socketSOCKET _sock ::socket(A…

pcb板材的tg是什么_做到这6点,PCB过回焊炉不会出现板弯及板翘!

【维文信PCBworld】在PCB板子过回焊炉容易发生板弯及板翘&#xff0c;大家都知道&#xff0c;那么如何防止PCB板子过回焊炉发生板弯及板翘&#xff0c;下面就为大家阐述下&#xff1a;1.降低温度对PCB板子应力的影响既然「温度」是板子应力的主要来源&#xff0c;所以只要降低回…

计算机编程试讲教案,2016教师资格证面试试讲高中信息技术教案:QBASIC分支结构程序...

更多真题资料及报考疑问请加老师微信66746005&#xff0c;或点击这里在线咨询教师资格证面试时间临近&#xff0c;推荐报名教师资格证面试网校高清课程(点击购买&#xff0c;随报随学&#xff0c;不限时间次数。免费试听)&#xff0c;来系统学习提升面试技能。关于教师资格证面…

java-ee-api_刷新器-Java EE 7概览

java-ee-api随着红帽JBoss企业应用平台7&#xff08;EAP 7&#xff09;的迫在眉睫&#xff0c;甚至WebLogic Server刚刚获得Java EE 7认证&#xff0c;我认为现在应该是时候对Java Enterprise Edition 7进行一些更新了。功能&#xff0c;并指向更多资源。 Java EE 7 –开发人员…

创建进程(CreateProcess)

下面来分享一下如何用C创建进程&#xff08;开发环境建议VS2017【宇宙第一IDE】&#xff09; /*以下为创建新的进程&#xff0c;以拉起记事本为例*/ #include <windows.h> #include <iostream> using namespace std; int main() {char TEST[] "notepad.exe…

层 数据仓库_小尝试:基于指标体系的数据仓库搭建和数据可视化

关于作者&#xff1a;小姬&#xff0c;某知名互联网公司产品专家&#xff0c;对数据采集、生产、加工有所了解&#xff0c;期望多和大家交流数据知识&#xff0c;以数据作为提出好问题的基础&#xff0c;挖掘商业价值。0x00 前言我将整理文章分享数据工作中的经验&#xff0c;因…

计算机中级职称报考入口,2020年9月计算机职称考试报名流程(附入口)

2020年9月计算机等级报名公告正在陆续公布&#xff0c;计算机等级考试网上怎么报名&#xff1f;小编为大家整理计算机等级报名流程如下&#xff1a;2020年9月计算机等级报名流程一、注册通行证考生登入报名网页后&#xff0c;点击“用户注册”按钮&#xff0c;根据网页提示&…

简单工厂抽象工厂工厂方法_让工厂美丽

简单工厂抽象工厂工厂方法每个名副其实的Java程序员都知道Factory Pattern 。 这是一种便捷&#xff0c;标准化的方法&#xff0c;它通过教一个组件如何捕鱼而不是将它们交给它们来减少耦合。 但是&#xff0c;在使用大型系统时&#xff0c;该模式确实会向系统添加很多样板代码…

枚举所有进程及其ID

#include<Windows.h>#include<TlHelp32.h> //声明快照函数的头文件#include<stdio.h>int main(int argc, char*argv[]){system("color 0A"); //设置底色PROCESSENTRY32 pe32; //声明该结构体变量//在使用这个结构前&#xff0…

内蒙古大学计算机组成原理实验,内蒙古大学计算机组成原理期末练习0

内蒙古大学计算机组成原理期末练习0 (4页)本资源提供全文预览&#xff0c;点击全文预览即可全文预览,如果喜欢文档就下载吧&#xff0c;查找使用更方便哦&#xff01;9.9 积分第 1 页 共 4 页 《《计算机组成原理计算机组成原理》》期末练习期末练习 班级 专业 姓名 学号 编号 …

dev的编辑器不支持getchar吗_“两头婚兴起”:你支持不娶不嫁,孩子随父姓也随母姓吗?...

何为两头婚&#xff1f;简单来说就是男不娶&#xff0c;女不嫁&#xff0c;没有彩礼也没有嫁妆的说法。男女俩人结婚之后没有谁到谁家里之说&#xff0c;两夫妻过自己的小日子&#xff0c;与各自的原生家庭保持一定的联系。婚后生育两个孩子&#xff0c;一个随父姓&#xff0c;…

字符串操作之字符串拷贝功能实现

#include<iostream> using namespace std; //思路分析&#xff1a; //a.确定被拷贝的数组内存是否小于等于目标数组的内存大小 //b.拷贝数组 //c.目标数组最后一个字符为空字符(\0)//str1:被拷贝的数组 str2:目标数组 len:目标数组的长度 bool stringCopy(char* str1, ch…

2015年传智播客java_2015年的Java –重大事件

2015年传智播客java2015年是Java的一年&#xff0c;语言&#xff0c;平台&#xff0c;生态系统和社区继续主导着软件领域&#xff0c;只有Javascript对整个行业产生了类似的影响。 如果您错过了2015年的亮点&#xff0c;这里是发生的一些重大事件。 Java 20岁了&#xff0c;还…

vue 删除两个集合中相同的数据_vue.js如何删除数组里面的数据

vue.js如何删除数组里面的数据发布时间&#xff1a;2020-12-10 12:04:44来源&#xff1a;亿速云阅读&#xff1a;107作者&#xff1a;小新这篇文章主要介绍了vue.js如何删除数组里面的数据&#xff0c;具有一定借鉴价值&#xff0c;需要的朋友可以参考下。希望大家阅读完这篇文…

字符串操作之字符串连接

/* 将str1与str2连接—->(将合成的字符串放入str1中) 思路分析: a. 确定str1的内存大小是否小于str1str2-1; b. 连接; c. 连接后的字符串最后一个为空字符(‘\0’)。 */ //str1-->存放最终连接字符串的数组 ,len--->str1的内存大小 #include<iostream> using na…

全国计算机证件照要露耳朵吗,结婚证照片要露耳朵么 结婚登记照露一只耳朵行吗...

结婚证件照&#xff0c;这是可以在在民政局里面照的&#xff0c;也是可以在外面的影楼照的&#xff0c;在外面影楼照的话&#xff0c;照出来会比较好看&#xff0c;就是价格有点贵。结婚证上面的照片是证件照&#xff0c;所以是有一定的要求的。露一只耳朵行不行呢&#xff1f;…

findfirst_当心findFirst()和findAny()

findfirst过滤Java 8 Stream &#xff0c;通常使用findFirst()或findAny()来获取在过滤器中幸存的元素。 但这可能并不能真正实现您的意思&#xff0c;并且可能会出现一些细微的错误。 那么 从我们的Javadoc&#xff08; 此处和此处 &#xff09;可以看出&#xff0c;这两种方…

儿童学python下哪个软件好用_【开源软件】超好用的Python学习软件!没有之一!...

可以观看视频&#xff01;文章结尾&#xff0c;有下载、安装方法&#xff01;曾几何时&#xff01;Python开始掀起了一阵狂猛的学习浪潮&#xff1f;Python那么Python为什么那么火&#xff1f;https://wenwen.sogou.com/z/q889141941.htm咱们在网上随便搜索&#xff0c;就是一大…

土木计算机2级,请教各位,我是学土木的,考计算机二级的哪个比较好?

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼谁听说近来是开学季&#xff0c;听度娘说开学季有三倍经验&#xff0c;竟然有三倍经验&#xff1f;但是就算有三倍经验我也不会借这谁听说近来是开学季&#xff0c;听度娘说开学季有三倍经验&#xff0c;竟然有三倍经验&#xff1f…

javafx_JavaFX在这里留下来!

javafx上周在网上看到了一些有关JavaFX未来的讨论。 许多人给人以Oracle将JavaFX搁置的印象。 这主要是由Shai Almog&#xff08;代号为One&#xff09;撰写的博客文章“ Should Oracle Spring Clean JavaFX”引起的。 我早些时候写了一个博客&#xff0c;“它的灵感”来自于我…