单元测试中使用Mockito模拟对象
单元测试应该小巧玲珑,轻盈快捷。然而,一个待测的对象可能依赖另一个对象。它可能需要跟数据库、邮箱服务器、Web Service、消息队列等服务进行交互。但是,这些服务可能在测试过程中不可用。假设这些服务可用,依赖这些服务的单元测试可能相当耗时。要是
- Web Service 不可获得。
- 数据库因维护而关闭。
- 消息队列笨重且缓慢。
这些违背单元测试小巧玲珑,轻盈快捷的初衷。单元测试被期待在几毫秒内执行完成。若单元测试缓慢,你的开发过程受阻,这会影响你开发组的效率。解决之道就是模拟(Mocking),
若你遵循OOP的SOILD原则,且使用Spring的依赖注入,单元测试中的模拟Mock变得轻而易举。你不必连接数据库。你只需一个能返回你期待值的对象。若你编写紧密耦合代码,模拟会相当艰难。我目睹过许多因紧密耦合其它对象的遗留代码不能单元测试。不可测试代码不遵循OOP的SOILD原则,且不能使用依赖注入。
Mockito初体验
接下来将学习使用Mockito框架。它是一套通过简单的方法对于指定接口或类生产Mock对象的类库。使用Mockito,在准备阶段只需少量时间,可以使用简洁的API编写漂亮的测试,可以对具体类创建Mock对象,并且有监视非Mock对象的功能。
这有两个术语需要了解一下。
Stub对象作用是在测试时提供所需的测试数据,可以对各种交互设置相应的回应。Mockito中
when(...).thenReturn(...)
这样的语法便是设置方法调用的返回值。另外也可以设置方法在何时调用会抛异常等。Mock对象用来验证测试中所依赖对象间的交互是否能够达到预期。Mockito中用
verify(...).methodXxx(...)
语法验证methodXxx()
方法是否按照预期进行调用。
需要加入到pom.xml的依赖如下:
<dependency><groupId>org.mockito</groupId><artifactId>mockito-core</artifactId><version>2.16.0</version><scope>test</scope>
</dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope>
</dependency>
创建Mock对象
可以通过两种方法来Mock对象
- 通过
mock(Class<T> clazz)
方法。 - 通过
@Mock
注解需要Mock的对象,然后调用MockitoAnnotations.initMocks(this)
或@RunWith(MockitoJUnitRunner.class)
初始化模拟。
//@RunWith(MockitoJUnitRunner.class)
public class MockitoSampleTest {// 模拟接口UserService mockUserService = mock(UserService.class);// 模拟实现类UserServiceImpl mockServiceImpl = mock(UserServiceImpl.class);// 基于注释模拟类@MockUser mockUser;@Beforepublic void initMocks() {// 初始化当前测试类所有@Mock注释模拟对象MockitoAnnotations.initMocks(this);}
}
值得注意的是,对于final类、匿名类和Java的基本类型是无法进行Mock的。
设定Mock对象的期望值行为及返回值
有两种通用基础设定写法:
when(...).thenReturn(...);
doReturn(...).when(...).someMethod();
但是,doReturn(...).when(mockObj.someMethod());
会抛异常。
@Test
public void testMockClass() {// 对方法设定返回值,也就是设置数据桩when(mockServiceImpl.findUserByUserName("tom")).thenReturn(new User("tom", "1234"));doReturn(true).when(mockServiceImpl).hasMatchUser("tom", "1234");User user = mockServiceImpl.findUserByUserName("tom");boolean isMatch = mockServiceImpl.hasMatchUser("tom", "1234");assertNotNull(user);assertEquals(user.getUserName(), "tom");assertEquals(isMatch, true);}
也值得注意的是,static和final修饰的方法无法进行设定的。
验证交互行为
Mock对象一旦建立便会自动记录自己的交互行为,所以,可以有选择地对其交互行为进行验证。
@Test
// 模拟接口UserService测试
public void testMockInterface() {// 对方法设定返回值,也就是设置数据桩when(mockUserService.findUserByUserName("tom")).thenReturn(new User("tom", "1234"));doReturn(true).when(mockUserService).hasMatchUser("tom", "1234");// 对void方法进行方法预期设定User u = new User("John", "1234");doNothing().when(mockUserService).registerUser(u);// 执行方法调用User user = mockUserService.findUserByUserName("tom");boolean isMatch = mockUserService.hasMatchUser("tom", "1234");mockUserService.registerUser(u);assertNotNull(user);assertEquals(user.getUserName(), "tom");assertEquals(isMatch, true);// 验证交互行为verify(mockUserService).findUserByUserName("tom");// 验证方法只调用一次verify(mockUserService, times(1)).findUserByUserName("tom");// 验证方法至少调用一次verify(mockUserService, atLeastOnce()).findUserByUserName("tom");verify(mockUserService, atLeast(1)).findUserByUserName("tom");// 验证方法至多调用一次verify(mockUserService, atMost(1)).findUserByUserName("tom");verify(mockUserService).hasMatchUser("tom", "1234");verify(mockUserService).registerUser(u);
}
对Service层进行单元测试
同常主要Java Web应用分Controller,Service,DAO基本三层来进行开发。
接下来通过使用Mockito框架对Service进单元测试。
Domain领域对象:
public class Product {}
DAO数据连接层:
public interface ProductDao {int getAvailableProducts(Product product);int orderProduct(Product product, int orderedQuantity);
}
Service业务逻辑层:
public class ProductService {private ProductDao productDao;public boolean buy(Product product, int orderedQuantity) {int availableQuantity = productDao.getAvailableProducts(product);if (orderedQuantity > availableQuantity) {return false;}productDao.orderProduct(product, orderedQuantity);return true;}}
Service测试用例:
public class ProductServiceTest {private ProductDao productDao;@Beforepublic void setupMock() {//模拟Dao层productDao = mock(ProductDao.class);}@Testpublic void testBuy() {int availableQuantity = 30;Product product = new Product();ProductService productService = new ProductService();//设置数据桩when(productDao.getAvailableProducts(product)).thenReturn(availableQuantity);//doReturn(availableQuantity).when(productDao).getAvailableProducts(product);//这写法不行//doReturn(availableQuantity).when(productDao.getAvailableProducts(product));//通过Spring测试框架提供的工具类为目标对象私有属性设值,这样就不用ProductDao另建setProductDao()方法ReflectionTestUtils.setField(productService, "productDao", productDao);Assert.assertFalse(productService.buy(product, 31));Assert.assertTrue(productService.buy(product, 3));//验证交互行为verify(productDao).orderProduct(product, 3);verify(productDao, times(2)).getAvailableProducts(product);}}
测试用例中,用到Spring test框架的ReflectionTestUtils
,它可以为目标对象非公有属性设值,或调用非公有setter方法,方便测试过程中使用。
为了使用ReflectionTestUtils
,需要向pom.xml添加下面的依赖
<dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>3.0.5.RELEASE</version><scope>test</scope>
</dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>3.0.5.RELEASE</version><scope>test</scope>
</dependency><dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.2</version><scope>test</scope>
</dependency>
总结
本文介绍了Mockito的基本用法,以及通过它Mock对象对Service层辅助测试用例。在Mockito辅助下,单元测试变得如虎添翼啊!
在编写代码过程中,必须反复调试它,保证他顺利通过。虽代码通过编译,只是说明其语法正确,但不能保证其语义也正确。没有任何人可以轻易承诺这段代码的行为一定是正确的。幸运的是,单元测试会为我们的承诺作出保证。编写单元测试就是用来验证这段代码的行为是否与我们期望的一样。有了单元测试,我们可以自信地交付自己的代码,减少后顾之忧。
引用
Mocking in Unit Tests with Mockito
Mockito (Mockito 2.16.0 API)
《Spring 3.x企业应用开发实战》陈雄华、林开雄 著
Spring Framework Reference Documentation 11. Testing
单元测试中使用Mockito模拟对象相关推荐
- Mock和Java单元测试中的Mock框架Mockito介绍
什么是Mock? 在面向对象程序设计中,模拟对象(英语:mock object,也译作模仿对象)是以可控的方式模拟真实对象行为的假的对象.程序员通常创造模拟对象(mock object)来测试其他对象 ...
- 单元测试中,模拟一个新对象
在单元测试中,会遇到有上百个属性的实体,而这些属性中,大多都是以String Int32 等类型为主,而如果模拟这个实体,给这个实体赋随机值,也要写上百行代码,效率极低 因此,我们可以通过反射来处理. ...
- mockito mock void方法_纯干货,浅谈Mockito在单元测试中的实际应用
本文接上文"接口方没写代码,对接方只能停工吗?",在这里简单介绍Mockito在单元测试中的实际应用.本文使用场景较单一,如有雷同,不甚荣幸,闲言少叙,开门见山.本文将使用mock ...
- 单元测试中简单使用Mockito解决Spring Bean依赖树问题
单元测试中简单使用Mockito解决Spring Bean依赖树问题 参考文章: (1)单元测试中简单使用Mockito解决Spring Bean依赖树问题 (2)https://www.cnblog ...
- Mockito 中被 Mocked 的对象属性及方法的默认值
在 Java 测试中使用 Mockito 有段时日了,以前只是想当然的认为 Mock 的对象属性值和方法返回值都是依据同样的规则.基本类型是 0, 0.0, 或 false, 对象类型都是 null, ...
- 单元测试中使用Mock对象
单元测试中使用Mock对象 单元测试中使用Mock对象 一.简单的替换 二.Mock 对象 三.测试 Servlet 单元测试中使用Mock对象 一.简单的替换 假设在代码中,你调用你自己的 getT ...
- 单元测试Mock框架--Mockito
文章目录 目前开发中,单元测试遇到的问题 解决方案--Mock Junit4 + Mockito: Mockito常用注解: Mockito常用方法: Tips: 总结 目前开发中,单元测试遇到的问题 ...
- 单元测试工具之Mockito
Mockito作用 Mockito 是一个针对 Java 的单元测试模拟框架,是为了简化单元测试过程中测试上下文 ( 或者称之为测试驱动函数以及桩函数 ) 的搭建而开发的工具.在有这些模拟框架之前,为 ...
- 如何使用Mockito模拟void方法
如何用void返回类型模拟方法? 我实现了一个观察者模式,但是我不能用Mockito模拟它,因为我不知道怎么做. 我试图在互联网上找到一个例子,但没有成功. 我的课看起来像这样: public cla ...
最新文章
- 重装失败、PE不能使用、重装很卡 个人电脑故障处理记录
- stm32使用 ST-LINK Utility量产,程序读保护、写保护
- repne scasb 内联实现 strlen
- JDK1.6官方下载_JDK6官方下载地址
- VC 6中使用不同调用规范的函数在符号文件里的表示方式
- php 实现 model层,Thinkhphp5控制器调用的Model层的方法总结
- Timer类 和 TimerTask类。一种有用计时器工具。可以每隔相同时间去执行某任务一次。
- [码海拾贝 之JS] JS 之删除数组中的元素
- 利用Java制作背单词小应用
- 中考英语听说计算机考试满分,2020北京中考英语听说机考题型分值及满分技巧...
- lzg_ad:XPE中EWF组件
- dualbootpatcher下载_多系统软件Dual Boot Patcher教程(多图)
- win7“您可能没有权限使用网络资源”的解决办法
- 微信小程序:中老年用户群体的流量生意如何做?
- Ant Design 中覆盖组件样式
- Revit中添加了标高看不见原来轴网解决方法及轴网转化
- Windows Phone 7芒果更新
- 下列不属于噪声的是_[单选] 下列选项中不属于噪声的是()。
- 关联规则与购物篮分析实战
- 我如何成为高级软件工程师
热门文章
- matlab sort descend,详解Matlab中 sort 函数用法
- 电压源和电流的关联参考方向_结点电压法解题系列之四:电流源支路
- efishell无法开机shell_开机出现efi shell卡住不动了解决方法全集
- 【转】.NET 的 WebSocket 开发包比较
- 【转】学习Entity Framework 中的Code First
- 关于DateTime的一点记录 ToString(yyyy-MM-dd HH:mm:ss)
- C#的变迁史08 - C# 5.0 之并行编程总结篇
- 一步步编写操作系统 20 x86虚拟bochs一般用法 上
- c语言哪个方法称为程序大门,学会这8个经典小程序,就相当于跨入了C语言大门...
- Qt undefined reference to,Error 255,找不到库函数的解决办法