一、什么是 Mock 测试

Mock 测试就是在测试过程中,对于某些不容易构造(如 HttpServletRequest 必须在Servlet 容器中才能构造出来)或者不容易获取比较复杂的对象(如 JDBC 中的ResultSet 对象),用一个虚拟的对象(Mock 对象)来创建以便测试的测试方法。Mock 最大的功能是帮你把单元测试的耦合分解开,如果你的代码对另一个类或者接口有依赖,它能够帮你模拟这些依赖,并帮你验证所调用的依赖的行为。
先来看看下面这个示例:

从上图可以看出如果我们要对A进行测试,那么就要先把整个依赖树构建出来,也就是BCDE的实例。
一种替代方案就是使用mocks

从图中可以清晰的看出
mock对象就是在调试期间用来作为真实对象的替代品。
mock测试就是在测试过程中,对那些不容易构建的对象用一个虚拟对象来代替测试的方法就叫mock测试。

二、Mockito是什么

Mockito 是一个流行 mock 框架,可以和JUnit结合起来使用。Mockito 允许你创建和配置 mock 对象。使用Mockito可以明显的简化对外部依赖的测试类的开发。

I notice that Mockito was voted "the best mock framework for Java" on Stackoverflow.

三、使用方法

1、添加Maven依赖

<!-- mockito -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.8.5</version>
<scope>test</scope>
</dependency>

如果使用springboot的话,有自带可以不用引入

2、建议静态导入会使代码更简洁

import static org.mockito.Mockito.*;
import static org.junit.Assert.*;

3、例子

3.1 创建一个mock对象,并校验

@Test
public void Demo1() {
// mock creation 创建mock对象
List mockedList = mock(List.class); //using mock object 使用mock对象
mockedList.add("one");
mockedList.clear(); //verification 验证
verify(mockedList).add("one");
verify(mockedList).clear();
}

3.2 使用测试桩
默认情况下,所有的函数都有返回值。mock函数默认返回的是null,一个空的集合或者一个被对象类型包装的内置类型,例如0、false对应的对象类型为Integer、Boolean;
测试桩函数可以被覆写 : 例如常见的测试桩函数可以用于初始化夹具,但是测试函数能够覆写它。请注意,覆写测试桩函数是一种可能存在潜在问题的做法;
一旦测试桩函数被调用,该函数将会一致返回固定的值;
上一次调用测试桩函数有时候极为重要-当你调用一个函数很多次时,最后一次调用可能是你所感兴趣的。

@Test
public void Demo2() {
// 你可以mock具体的类型,不仅只是接口
LinkedList mockedList = mock(LinkedList.class); // 测试桩(可以使用连续调用)
when(mockedList.get(0)).thenReturn("first");
//when(mockedList.get(0)).thenReturn("first").thenReturn("second");
when(mockedList.get(1)).thenThrow(new RuntimeException()); // 输出“first”
System.out.println(mockedList.get(0));
// 连续调用则输出“second”
//System.out.println(mockedList.get(0)); // 抛出异常
System.out.println(mockedList.get(1)); // 因为get(999) 没有打桩,因此输出null
System.out.println(mockedList.get(999)); // 验证get(0)被调用的次数
verify(mockedList).get(0);
}

3.3 验证函数的确切、最少、从未调用次数

@Test
public void Demo3() {
// 你可以mock具体的类型,不仅只是接口
LinkedList mockedList = mock(LinkedList.class);
//using mock
mockedList.add("once"); mockedList.add("twice");
mockedList.add("twice"); mockedList.add("three times");
mockedList.add("three times");
mockedList.add("three times"); // 下面的两个验证函数效果一样,因为verify默认验证的就是times(1)
verify(mockedList).add("once");
verify(mockedList, times(1)).add("once"); // 验证具体的执行次数
verify(mockedList, times(2)).add("twice");
verify(mockedList, times(3)).add("three times"); // 使用never()进行验证,never相当于times(0)
verify(mockedList, never()).add("never happened"); // 使用atLeast()/atMost()
verify(mockedList, atLeastOnce()).add("three times"); // mockedList.add("five times");
// mockedList.add("five times");
// mockedList.add("five times");
// mockedList.add("three times");
// mockedList.add("three times");
// mockedList.add("three times");
//最少or最多
verify(mockedList, atLeast(2)).add("five times");
verify(mockedList, atMost(5)).add("three times"); }

3.4 模拟异常

@Test
public void Demo4() { LinkedList mockedList = mock(LinkedList.class);
doThrow(new RuntimeException()).when(mockedList).clear(); // 调用这句代码会抛出异常
mockedList.clear();
}

3.5 验证执行执行顺序

@Test
public void Demo5() {
// A. 验证mock一个对象的函数执行顺序
List singleMock = mock(List.class); singleMock.add("was added first");
singleMock.add("was added second");
singleMock.contains("111");
singleMock.isEmpty();
singleMock.remove(0);
singleMock.get(0); // 为该mock对象创建一个inOrder对象
InOrder inOrder = inOrder(singleMock); // 确保add函数首先执行的是add("was added first"),然后才是add("was added second")
inOrder.verify(singleMock).add("was added first");
inOrder.verify(singleMock).add("was added second");
inOrder.verify(singleMock).contains("111");
inOrder.verify(singleMock).isEmpty();
inOrder.verify(singleMock).remove(0);
inOrder.verify(singleMock).get(0); // // B .验证多个mock对象的函数执行顺序
// List firstMock = mock(List.class);
// List secondMock = mock(List.class);
//
// firstMock.add("was called first");
// secondMock.add("was called second");
//
// // 为这两个Mock对象创建inOrder对象
// InOrder inOrder = inOrder(firstMock, secondMock);
//
// // 验证它们的执行顺序
// inOrder.verify(firstMock).add("was called first");
// inOrder.verify(secondMock).add("was called second"); // Oh, and A + B can be mixed together at will
}

3.6 spy
然而很多时候,你希望达到这样的效果:除非指定,否者调用这个对象的默认实现,同时又能拥有验证方法调用的功能。这正好是spy对象所能实现的效果。创建一个spy对象,以及spy对象的用法介绍如下:

@Test
public void Demo6() { List list = new LinkedList();
List spy = spy(list); // 你可以为某些函数打桩
when(spy.size()).thenReturn(100); // 通过spy对象调用真实对象的函数
spy.add("one");
spy.add("two"); // 输出第一个元素
System.out.println(spy.get(0)); // 因为size()函数被打桩了,因此这里返回的是100
System.out.println(spy.size()); // 交互验证
verify(spy).add("one");
verify(spy).add("two");
}

3.7 在监控对象上使用when(Object)来进行打桩是不可能或者不切实际的。
因此,当使用监控对象时请考虑doReturn|Answer|Throw()函数族来进行打桩。

@Test
public void Demo7(){
List list = new LinkedList();
List spy = spy(list); // 不可能 : 因为当调用spy.get(0)时会调用真实对象的get(0)函数,此时会发生IndexOutOfBoundsException异常,因为真实List对象是空的
//when(spy.get(0)).thenReturn("foo"); // 你需要使用doReturn()来打桩
doReturn("foo").when(spy).get(anyInt()); System.out.println(spy.get(0));
// doThrow(new RuntimeException()).when(spy).get(anyInt());
// System.out.println(spy.get(0));
}

3.8使用注解形式来模拟测试对象,必须在初始化fields (领域),有2种方式初始化:

@RunWith(@MockitoJUnitRunner.class) 标注 JUnit 测试类
@Before 之前调用 MockitoAnnotations.initMocks(Object)
@Before
public void init(){
MockitoAnnotations.initMocks(this);
}

或者

@RunWith(MockitoJUnitRunner.class)

这里补充一下几个注解的作用

一般项目测试是用@RunWith(SpringRunner.class)

这个运行器来启动的,有些教程使用的是SpringJUnit4ClassRunner这个运行器,我们可以查看源码,其实是同一个东西,

public final class SpringRunner extends SpringJUnit4ClassRunner {
public SpringRunner(Class<?> clazz) throws InitializationError {
super(clazz);
}
}

3.9 参数捕获

ArgumentCaptor类允许我们在verification期间访问方法的参数。得到方法的参数后我们可以使用它进行测试。
ArgumentCaptor是一个能够捕获参数值的特殊参数匹配器。捕获一个
Mock 对象的方法调用所传递的参数

@Test
public void testCaptureArgument() {
List<String> list = Arrays.asList("1", "2");
List mockedList = mock(List.class);
ArgumentCaptor<List> argument = ArgumentCaptor.forClass(List.class);
mockedList.addAll(list);
verify(mockedList).addAll(argument.capture()); assertEquals(2, argument.getValue().size());
assertEquals(list, argument.getValue());
}

3.10 RETURNS_SMART_NULLS 和 RETURNS_DEEP_STUBS

RETURNS_SMART_NULLS

(返回默认值-查看源码: ReturnsMoreEmptyValues)
RETURNS_SMART_NULLS
在创建mock对象时,有的方法我们没有进行stubbing,所以在调用的时候有时会返回Null这样在进行处理时就很可能抛出NullPointerException。如果通过RETURNS_SMART_NULLS参数来创建的mock对象在调用没有stubbed的方法时他将返回SmartNull。例如:返回类型是String它将返回空字符串””;是int,它将返回0;如果是List,它会返回一个空的List。另外,在堆栈中可以看到SmartNull的友好提示。

由于使用了RETURNS_SMART_NULLS参数来创建mock对象,所以在执行下面的操作时将不会抛出NullPointerException异常,另外堆栈也提示了相关的信息“SmartNull returned by unstubbed get() method on mock”。

@Test
public void returnsSmartNullsTest() {
//List mock = mock(List.class);
List mock = mock(List.class, RETURNS_SMART_NULLS);
System.out.println(mock.get(0)); // 使用RETURNS_SMART_NULLS参数创建的mock对象,不会抛出NullPointerException异常。
// 另外控制台窗口会提示信息“SmartNull returned by unstubbed get() method on mock”
System.out.println(mock.toArray().length);
}

RETURNS_DEEP_STUBS

(判断是否需要返回默认值,否则创建mock对象 ReturnsDeepStubs)
RETURNS_DEEP_STUBS参数程序会自动进行mock所需的对象,方法deepstubsTest和deepstubsTest2是等价的

public class FakeEntity {
private UserFakeEntity userFakeEntity; public UserFakeEntity getUserFakeEntity() {
return userFakeEntity;
} public void setUserFakeEntity(UserFakeEntity userFakeEntity) {
this.userFakeEntity = userFakeEntity;
}
} @Test
public void deepstubsTest() {
//FakeEntity fakeEntity = mock(FakeEntity.class);//这样会NullPointerException
FakeEntity fakeEntity = mock(FakeEntity.class, RETURNS_DEEP_STUBS);
when(fakeEntity.getUserFakeEntity().getName()).thenReturn("Beijing");
System.out.println(fakeEntity.getUserFakeEntity().getName());
assertEquals("Beijing", fakeEntity.getUserFakeEntity().getName());
} @Test
public void deepstubsTest2() {
FakeEntity fakeEntity = mock(FakeEntity.class);
UserFakeEntity userFakeEntity = mock(UserFakeEntity.class); when(fakeEntity.getUserFakeEntity()).thenReturn(userFakeEntity);
when(userFakeEntity.getName()).thenReturn("Beijing");
System.out.println(fakeEntity.getUserFakeEntity().getName());
assertEquals("Beijing", fakeEntity.getUserFakeEntity().getName());
}

四、实战使用

4.1登录的例子

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.boot.test.context.SpringBootTest; import static org.mockito.Mockito.*; //基于MockitoJUnitRunner的运行器
@RunWith(MockitoJUnitRunner.class)
//@RunWith(SpringRunner.class)
@SpringBootTest
public class MockLoginTest { //自动将模拟对象或侦查域注入到被测试对象中。
//对被测类中@Autowired的对象,用@Mocks标注;对被测类自己,用@InjectMocks标注
@InjectMocks
private UserServiceImpl userService; @Mock
private UserRepository userDao; @Mock
private BargainBlackListService bargainBlackListService; @Mock
private DistributionUserService distributionUserService; @Mock
private AuthorityService authorityService; @Before
public void init(){
MockitoAnnotations.initMocks(this);
} @Test
public void loginTest() {
//模拟方法动作
//spyUserService在调用getUserEntity方法的时候,指定返回事先定义好的userEntity
UserService spyUserService = spy(userService);
UserEntity userEntity = new UserEntity();
userEntity.setUserName("ppp");
doReturn(userEntity).when(spyUserService).getUserEntity(anyString());
when(bargainBlackListService.checkBlackList(anyString())).thenReturn(true);
UserInfoDTO userInfoDTO = new UserInfoDTO();
userInfoDTO.setUserName("ppp");
userInfoDTO.setId("2c95808a644ade6801644ae37f730000");
spyUserService.updateUserInfo("0520b1d9-9806-4ec1-a095-dfcd8bea8fd6", userInfoDTO);
} }

mockito使用教程的更多相关文章

  1. mockito简单教程

    注:本文来源:sdyy321的<mockito简单教程> 官网: http://mockito.org API文档:http://docs.mockito.googlecode.com/h ...

  2. 使用 Mockito 单元测试 – 教程

    tanyuanji@126.com 版本历史 - - - - 使用 Mockito 进行测试 该教程主要讲解 Mockito 框架在Eclipse IDE 中的使用   目录 tanyuanji@12 ...

  3. Mockito 简明教程

    什么是 Mock 测试 Mock 测试就是在测试过程中,对于某些不容易构造(如 HttpServletRequest 必须在Servlet 容器中才能构造出来)或者不容易获取比较复杂的对象(如 JDB ...

  4. Mockito教程

    Mockito教程 2017-01-20 目录 1 Mockito 介绍   1.1 Mockito是什么?  1.2 为什么需要Mock  1.3 Stub和Mock异同  1.4 Mockito资 ...

  5. Mockito 简介

    Mockito 是一种 Java Mock 框架,主要是用来做 Mock 测试,它可以模拟任何 Spring 管理的 Bean.模拟方法的返回值.模拟抛出异常等等,在了解 Mockito 的具体用法之 ...

  6. 学习 Spring Boot:(二十九)Spring Boot Junit 单元测试

    前言 JUnit 是一个回归测试框架,被开发者用于实施对应用程序的单元测试,加快程序编制速度,同时提高编码的质量. JUnit 测试框架具有以下重要特性: 测试工具 测试套件 测试运行器 测试分类 了 ...

  7. 【项目经验】Mockito教程

    一.教程 转载:https://blog.csdn.net/sdyy321/article/details/38757135/ 官网: http://mockito.org API文档:http:// ...

  8. Mockito框架入门教程(一)

    官网: http://mockito.org API文档:http://docs.mockito.googlecode.com/hg/org/mockito/Mockito.html 项目源码:htt ...

  9. Mockito框架入门教程(二)

    接上一篇,继续学习其它的.... 8.找出冗余的互动(即未被验证到的) @Test(expected = NoInteractionsWanted.class) public void find_re ...

随机推荐

  1. 【python语法基础-经典练习题】python语法基础练习题01---商场打折

    # 1.一家商场在降价促销.如果购买金额50-100元(包含50元和100元)之间,会给10%的折扣(打九折),# 如果购买金额大于100元会给20%折扣.编写一程序,询问购买价格,再显示出折扣(%1 ...

  2. CSS 实现一个自适应的正方形

      传统方法正方形用固定的形式写 直接长=宽写固定的值如下   .box{   width: 200px;   height: 200px;   background: pink;   color: ...

  3. 我的第一个原生Web Components——滑块(SingleSlider)

    写着写着,就会跑偏,没错又走上了一个岔道……就是不知道这条岔道以后会不会越来越宽,有的说他是未来,有的说…… 这里不知道,也不做什么评断.减少一些重复性的工作,提高开发效率这是最根本的.说白了就是偷懒 ...

  4. FZU-Problem 2150 Fire Game(两点bfs)

    Fat brother and Maze are playing a kind of special (hentai) game on an N*M board (N rows, M columns) ...

  5. day03_1spring3

    事务管理的几种方式.spring整合Junit.spring整合web.ssh整合 一.事务管理的几种方式: 1.介绍前提我们需要导入:spring-tx-3.2.0.RELEASE.jar的包里面含 ...

  6. vjudge A^B Mod C 然后,10.6最。。。的 快速幂!!!

    链接:https://vjudge.net/contest/331993#problem/D 给出3个正整数A B C,求A^B Mod C. 例如,3 5 8,3^5 Mod 8 = 3. Inpu ...

  7. ABC155 D pair 边界处理取整

    ABC155 D pair 取整坑点 思路 很常见的一道题,二分找答案,然后看这个答案排rank?,排rank?用二分继续找一遍二分套二分即可,就是边界比较烦,老年人写的心情烦躁 老年人被取整坑的几天 ...

  8. LED Holiday Light -holiday Light Inspection Implementation Recommendations

    China LED Holiday Light Factory & Ninghai County Haohua Electronic Appliance Co., Ltd. pointed o ...

  9. How to do high impact research + 实事求是

    1. develop a strong publications record early, so do what you can to make that happen. 2. 粗读:abstrac ...

  10. LINUX使用SSH远程终端时,如何将运行时间长的程序在后台挂起,下次SSH登陆时继续使用同一个SHELL?

    我在某个平台上购买了一个云服务器,LINUX操作系统无图形化界面,硬盘空间较小.虽然在平台上可以通过其自带网页版VNC界面登陆SHELL进而操控云主机,但是每次需要操控都得打开网页登陆进平台,然后再进 ...