如何模拟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
。 配置文件aop
在AddressLogger
方面确保相同。 当我们自动测试对象UserService
及其依赖项AddressService
,我们知道可以将其spyDelegate
为AddressServiceSpy
并在调用测试方法后验证其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(版本2)相关推荐
- spring-bean版本_如何模拟Spring bean(版本2)
spring-bean版本 大约一年前,我写了一篇博客文章如何模拟Spring Bean . 所描述的模式对生产代码几乎没有侵入性. 正如读者Colin在评论中正确指出的那样,基于@Profile注释 ...
- 如何在没有Springockito的情况下模拟Spring bean
我在Spring工作了几年. 但是我总是对XML配置变得多么混乱感到沮丧. 随着各种注释和Java配置可能性的出现,我开始喜欢使用Spring进行编程. 这就是为什么我强烈使用Java配置的原因. 我 ...
- Spring Bean 中的线程安全
在 使用Spring框架时,很多时候不知道或者忽视了多线程的问题.因为写程序时,或做单元测试时,很难有机会碰到多线程的问题,因为没有那么容易模拟多线 程测试的环境.但如果不去考虑潜在的漏洞,它就会变成 ...
- 模拟spring - 简单实现spring IOC
一.前言 IOC (Inverse of control) - 控制反转,spring的IOC实现原理为利用Java的反射机制并充当工厂的角色完成对象的装配和注入. 二.实现细节 附上一张类的结构图, ...
- 【一步一步学习spring】spring bean管理(上)
1. spring 工厂类 我们前边的demo中用到的spring 工厂类是ClassPathXmlApplicationContext,从上图可以看到他还有一个兄弟类FileSystemApplic ...
- java反射机制(三)---java的反射和代理实现IOC模式 模拟spring
IOC(Inverse of Control)可翻译为"控制反转",但大多数人都习惯将它称为"依赖注入".在Spring中,通过IOC可以将实现类.参数信息等配 ...
- Java程序员从笨鸟到菜鸟之(六十八)细谈Spring(二)自己动手模拟spring
在我们学习spring之前,根据spring的特性,我们来自己来模拟一个spring出来,也就是说不利用spring来实现spring的效果.本实例主要是实现spring的IOC功能. 点击下载源码: ...
- Spring事务专题(四)Spring中事务的使用、抽象机制及模拟Spring事务实现
前言 本专题大纲如下: 事务专题大纲 「对于专题大纲我又做了调整哈,主要是希望专题的内容能够更丰富,更加详细」,本来是想在源码分析的文章中附带讲一讲事务使用中的问题,这两天想了想还是单独写一篇并作为事 ...
- Spring bean注入之注解注入-- @Autowired原理
之前我们已经讲述过bean注入是什么了,也使用了xml的配置文件进行bean注入,这也是Spring的最原始的注入方式(xml注入). 本节课就讲注解注入. 主要讲解的注解有以下几个: @Autowi ...
最新文章
- 工业4.0是个白日梦吗?
- Mybatis分页插件 - 示例
- SVN服务器部署并实现双机同步及禁止普通用户删除文件
- Webpack不生成index.html
- spring mvc 教程_Spring MVC开发–快速教程
- Android 沉浸式状态栏
- 需求管理-需求的结构
- 中科大软件学院硕士:实习秋招百多轮面试总结(中)
- java 对象快速赋值_JavaWeb学习笔记:简单JavaBean对象的快速赋值与获取
- jquery 实现四级联动
- Android Jetpack系列--8. DataStore使用详解
- 【小程序】rpx(responsive pixel)自适应像素浅析
- javascript实现数独解法
- always_comb,always_ff,和always_latch语句
- 大前端CPU优化技术--NEON编程优化技巧
- mysql查询每个分组的最新数据
- 敲开bp神经网络之门(三,机器视觉斑点blob匹配中使用)
- 通过C#Microsoft.Office.Interop.Word理解互操作性
- 1468. 计算税后工资(SQL)
- 今天看到了三年前杭电的LCY老师写的一篇自我检讨,值得大家来读一读
热门文章
- Junit5新功能一览
- 公众号一年能有多少收入?
- mybatis报错:java.lang.IllegalArgumentException: Mapped Statements collection does not contain
- 关闭(杀死)8080端口
- android查看wifi是否双频,Android判断wifi是5G还是2.4G
- 前端wxml取后台js变量值_这些鲜为人知的前端冷知识,你都GET了吗?
- bmp180气压传感器工作原理_陕西压力传感器的工作原理信息推荐
- nginx负载均衡与反向代理
- java泛型程序设计——约束与局限性
- web安全测试视频课程专题_有关有效企业测试的视频课程