文章目录
- 1.为什么需要Mock
- 2.Mockito 中常用方法
- 2.1 Mock 方法
- 2.2 对 Mock 出来的对象进行行为验证和Junit结果断言
- 2.3 测试桩stub
- 2.4 参数匹配器
- 2.5 mock()与spy()
- 2.6 @InjectMocks
本文参考:
【码农教程】手把手教你Mockito的使用 - 掘金 (juejin.cn)
java - doReturn().when()与when().thenReturn() - 成长之路 - SegmentFault 思否
单元测试实践篇:Mock_阿里巴巴淘系技术团队官网博客的博客-CSDN博客
阿里是如何进行单元测试培训的?_Hollis Chuang的博客-CSDN博客
【Mockito】Mockito + Junit 5 快速入门_哔哩哔哩_bilibili
1.为什么需要Mock
测试驱动的开发( TDD)要求我们先写单元测试,再写实现代码。在写单元测试的过程中,我们往往会遇到要测试的类有很多依赖,这些依赖的类/对象/资源又有别的依赖,从而形成一个大的依赖树,要在单元测试的环境中完整地构建这样的依赖,是一件很困难的事情。如下图所示:
为了测试类A,我们需要Mock B类和C类(用虚拟对象来代替)如下图所示:
2.Mockito 中常用方法
先添加maven依赖:
mockito和junit:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jxz</groupId>
<artifactId>MockitoLearning</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.3.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
</dependencies>
</project>
2.1 Mock 方法
mock 方法来自 org.mockito.Mock
,它表示可以 mock 一个对象或者是接口。
public static <T> T mock(Class<T> classToMock)
- classToMock:待 mock 对象的 class 类。
- 返回 mock 出来的类
实例:使用 mock 方法 mock 一个List类
List mockList = Mockito.mock(List.class);
也可以使用注解来快速模拟
- @Mock+MockitoAnnotations.openMocks(this)
也有用@Mock+MockitoAnnotations.initMocks(this)的
package com.jxz;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.List;
import static org.mockito.Mockito.verify;
/**
* @Author jiangxuzhao
* @Description
* @Date 2023/6/19
*/
public class MockExample1 {
@Mock
private List mockList;
@Before
public void setup(){
MockitoAnnotations.openMocks(this);
}
@Test
public void testMock(){
mockList.add(1);
verify(mockList).add(1);
}
}
- @Mock+@RunWith(MockitoJUnitRunner.class)
package com.jxz;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.List;
import static org.mockito.Mockito.verify;
/**
* @Author jiangxuzhao
* @Description
* @Date 2023/6/19
*/
@RunWith(MockitoJUnitRunner.class)
public class MockExample2 {
@Mock
private List mockList;
@Test
public void testMock(){
mockList.add(1);
verify(mockList).add(1);
}
}
2.2 对 Mock 出来的对象进行行为验证和Junit结果断言
一旦mock对象被创建了,Mock对象会记录我们调用的所有交互,也就是各种方法和参数,验证的意思是”查看我们到底有没有调用过mock的这个方法“,Mockito 中验证的方法是:verify
package com.jxz;
import org.junit.Test;
import org.mockito.Mockito;
import java.util.List;
import static org.mockito.Mockito.verify;
/**
* @Author jiangxuzhao
* @Description
* @Date 2023/6/19
*/
public class Demo {
@Test
public void testMock(){
List mockList = Mockito.mock(List.class);
mockList.add(1);
mockList.add("one");
// verify
verify(mockList).add(1);
// verify(mockList).add("two"); //会抛出异常
}
}
JUnit断言使用到的类是 Assert.
@Test
public void testMock2(){
List mockList = Mockito.mock(List.class);
mockList.add(1);
Assert.assertEquals(1,mockList.get(1));
}
输出:
java.lang.AssertionError:
Expected :1
Actual :null
当使用 mock 对象时(这里是mockList),如果不对其行为进行定义(也就是下面的打桩),则 mock 对象方法的返回值为返回类型的默认值(这里为null)。
2.3 测试桩stub
指定mock对象的交互行为逻辑,基本格式when().thenReturn()
when(mockedList.get(0)).thenReturn(“first”)规定当调用get(0)的时候返回"first"
@Test
public void testStub(){
LinkedList mockList = Mockito.mock(LinkedList.class);
// stubbing
when(mockList.get(0)).thenReturn("first");
when(mockList.get(1)).thenThrow(new RuntimeException());
System.out.println(mockList.get(0)); //first
System.out.println(mockList.get(1)); //RuntimeException
}
当我们连续两次为同一个方法使用stub的时候,他只会只用最新的一次。一旦这个方法被stub了,就会一直返回这个stub的值。如下:
@Test
public void testStub2(){
LinkedList mockList = Mockito.mock(LinkedList.class);
when(mockList.get(0)).thenReturn("first");
when(mockList.get(0)).thenReturn("second");
System.out.println(mockList.get(0));
System.out.println(mockList.get(0));
}
输出:
second
second
还有一种测试桩指定的方式,即doReturn().when()
,本质上也是规定行为。
@Test
public void testDoReturn(){
A a = new A();
A mockA = Mockito.mock(A.class);
Mockito.when(mockA.add(1,2)).thenReturn(5); // 当mockA调用add(1,2),返回5
System.out.println(mockA.add(1,2)); // 5
A A2 = new A();
A mockA2 = Mockito.mock(A.class);
Mockito.doReturn(5).when(mockA2).add(1,2); // 同样是当mockA调用add(1,2),返回5
System.out.println(mockA2.add(1,2)); // 5
}
两者本质上就是一个由于执行顺序产生的问题,Mockito.when(mockA.add(1,2)).thenReturn(5)
,会先执行a + b
,即 1 + 2
, 结果本应是3,但由于后面的thenReturn
,所以调用该方法时,实际的返回值是5。而Mockito.doReturn(5).when(mockA2).add(1,2)
就不会执行a+b
的操作,而会直接返回5。区别就是若是一定会执行add()方法,难免会产生无法预估的副作用,比如抛出异常等。
2.4 参数匹配器
参数匹配器可以进行参数的灵活指派。
@Test
public void testMatch(){
Map mockMap = Mockito.mock(Map.class);
// 正常打桩测试
when(mockMap.get("key1")).thenReturn("value1");
System.out.println(mockMap.get("key1")); //value1
// 任意String的参数匹配器
when(mockMap.get(anyString())).thenReturn("value2");
System.out.println(mockMap.get(anyString())); // value2
System.out.println(mockMap.get("key2")); // value2
System.out.println(mockMap.get(1)); // null
// 多个入参时,要么都使用参数匹配器,要么都不使用,否则会异常, put()返回参数
when(mockMap.put(anyString(),anyInt())).thenReturn("value3");
System.out.println(mockMap.put("key3",3)); // value3
System.out.println(mockMap.put(anyString(),anyInt())); // value3
// System.out.println("key3",anyInt()); // 异常
// verify也支持参数匹配
verify(mockMap,atLeastOnce()).get(anyString()); // 前面交互至少调用了一次get(anyString())
verify(mockMap).put(anyString(),eq(3)); // 前面至少调用了一次put(anyString(),3)
}
2.5 mock()与spy()
- 被 spy 的对象会走真实的方法,而 mock 对象走虚拟对象的方法,返回默认值
- spy() 方法的参数是对象实例,mock()方法 的参数是 class
示例:
package com.jxz;
import org.junit.Test;
import org.mockito.Mockito;
/**
* @Author jiangxuzhao
* @Description
* @Date 2023/6/20
*/
public class SpyAndMock {
@Test
public void testSpy(){
A a = new A();
A a1 = Mockito.mock(A.class);
A a2 = Mockito.spy(a);
System.out.println(a1.add(1,2));
System.out.println(a2.add(1,2));
}
}
class A{
public int add(int a, int b){
return a+b;
}
}
输出:
0
3
通过mock生成的对象,会拥有以前的对象的所有方法,但是方法中都没有了功能,就比如上面的a1
对应的类可以理解下面这样
A1 extend A {
pubic int add(int a, int b) {
return 0;
}
}
2.6 @InjectMocks
@InjectMocks和@Mock配合可以简化某个类中,注入类的配置。同时可以在外面对被@Mock的类进行行为指定,从而让其被调用时,产生我们指定的结果。
示例:
要测试的类和方法:
package com.jxz;
/**
* @Author jiangxuzhao
* @Description
* @Date 2023/6/20
*/
public class RegistrationImpl {
SalesDao salesDao = new SalesDao();
SaveDao saveDao = new SaveDao();
public String register(int id, String name){
String result1 = salesDao.findRep(name);
System.out.println(result1);
String result2 = saveDao.save(id, name);
System.out.println(result2);
return result1 + "_" + result2;
}
}
class SalesDao{
public String findRep(String name){
return name;
}
}
class SaveDao{
public String save(int id, String name){
return id + name;
}
}
对应的测试类和方法:
package com.jxz;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import static org.mockito.Mockito.when;
/**
* @Author jiangxuzhao
* @Description
* @Date 2023/6/20
*/
@RunWith(MockitoJUnitRunner.class)
public class RegistrationImplTest {
@InjectMocks
private RegistrationImpl registrationImpl; // 需要被注入mock对象的对象
@Mock
private SalesDao salesDao; // RegistrationImpl中注入的类
@Mock
private SaveDao saveDao;
@Test
public void testRegister(){
// 进行mock注入类的行为指定
// 可以看到register方法中调用mock对象打印出来的东西正确
when(salesDao.findRep("jiangxuzhao")).thenReturn("jiangxuzhao666"); // jiangxuzhao666
when(saveDao.save(123,"jiangxuzhao")).thenReturn("123jiangxuzhao666"); // 123jiangxuzhao666
String result = registrationImpl.register(123, "jiangxuzhao");
Assert.assertEquals(result,"jiangxuzhao666_123jiangxuzhao666"); // 结合mock对象的返回值正确
// Assert.assertEquals(result,"null"); // org.junit.ComparisonFailure
}
}