Mock 常用于单元测试,用来模拟测试代码所依赖的外部类。因为我们所编写的代码往往存在大量复杂的外部依赖,或者依赖于特定的环境才能正常运行。这时我们可以选择使用 Mock 来构造这些外部依赖,使它们产生符合我们期待的行为(例如返回特定的值),但不需要真正地去构造外部依赖。需要注意的是,Mock 出来的对象并不会真实执行。

Java 中常用的 Mock 框架为 Mockito 和 Powermock,Powermock 主要在 Mockito 上添加了 final、private、static 方法的支持,两者可以结合使用。

添加依赖

环境为 Java1.6、Junit4.4

添加 PowerMockito 的依赖,因为使用 PowerMockito 就不需要再导入 Mockito 的依赖了。

<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-module-junit4</artifactId>
    <version>2.0.2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-api-mockito2</artifactId>
    <version>2.0.2</version>
    <scope>test</scope>
</dependency>

注:如果是 Junit4.4 但是导入了powermock-module-junit4-legacy,在使用@RunWith(PowerMockRunner.class)时,会报org/junit/internal/runners/TestClassRunnerorg/junit/internal/runners/TestClassRunner的错。

详情:Mockito 2 Maven · powermock/powermock Wiki · GitHub

使用 Mock

有两种方式来 mock 一个类。

import static org.mockito.Mockito.*;
//1使用方法
PersonDao dao=mock(PersonDao.class);
//2使用注解
@Mock
PersonDao dao;

下面使用到的代码为:

被测试的代码:

public class PersonService {
    PersonDao personDao=new PersonDao();

    private static int num=3;
    private int num2=3;
    public void test(){
        String str=personDao.test("1");
        System.out.println(str);
    }

    public void testStatic(){
        String str=PersonDao.testStatic("1");
        System.out.println(str);
    }

    public void testSingle(){
        SingleInstance instance=SingleInstance.getInstance();
        String str=instance.test();
        System.out.println(str);
    }

    public void testVoid(){
        personDao.testVoid();
    }

    public int testVerify(){
        return 1;
    }

}

依赖代码:

public class PersonDao {
    public String test(String str){
        return "test:"+str;
    }

    public static String testStatic(String str){
        return "test:"+str;
    }

    public void testVoid(){}
}

mock 普通方法

import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class PersonServiceTest {
    @Mock
    private PersonDao personDao;

    @Test
    public void run(){
        when(personDao.test(anyString())).thenReturn("not find");
        System.out.println(personDao.test("1"));
    }
}
//输出: not find

Mockito 提供了很多方法来匹配参数,例如 anyString()anyInt()any(),而不必知道具体参数。也可以使用 eq() 方法来匹配具体的参数,例如:*when*(personDao.find(*eq*("1"))).thenReturn("not find");

when()...thenReturn()的过程被称为打桩(stub)。当执行 personDao.find()方法时,返回所指定的值。

需要注意打桩时,匹配参数要不全用具体值,要不全用Matchers提供,否则会报错:

org.mockito.exceptions.misusing.InvalidUseOfMatchersException: 
Invalid use of argument matchers!

Spy

mock 出来的类不会真正执行,但是 Spy 出来的方法会真正执行,但是仍然可以打桩。

@RunWith(MockitoJUnitRunner.class)
public class PersonServiceTest {
    @Spy
    private PersonDao personDao;

    @Test
    public void run(){
        System.out.println(personDao.test("1"));
    }
}
//输出 test:1

mock 出来的方法:

@RunWith(MockitoJUnitRunner.class)
public class PersonServiceTest {
    @Mock
    private PersonDao personDao;

    @Test
    public void run(){
        System.out.println(personDao.test("1"));
    }
}
//输出:null

mock 静态方法

mock 静态方法需要 PowerMock。

import static org.powermock.api.mockito.PowerMockito.*;
@PrepareForTest({PersonDao.class})
@RunWith(PowerMockRunner.class)
public class PersonServiceTest {

    @Test
    public void run(){
				//mock一个静态类,被测试代码依赖的类
        mockStatic(PersonDao.class);
				//当调用PersonDao.findStatic()方法,返回特定的值
        when(PersonDao.testStatic(Mockito.anyString())).thenReturn("not find");
				//被测试的类
        PersonService personService=new PersonService();
        personService.testStatic();
    }
}

私有属性的访问与修改

主要是使用到了 Whitebox 的 setInternalState()getInternalState() 方法,能访问、修改私有实例方法和私有静态方法。

私有实例方法,参数传入实例变量:

import static org.powermock.api.mockito.PowerMockito.*;
@RunWith(PowerMockRunner.class)
public class PersonServiceTest {

    @Test
    public void run(){
        PersonService personService=new PersonService();
        Whitebox.setInternalState(personService,"num2",5);
        int num=Whitebox.getInternalState(personService,"num2");
        System.out.println(num);
    }
}

私有静态方法,参数传入类:

import static org.powermock.api.mockito.PowerMockito.*;
@RunWith(PowerMockRunner.class)
public class PersonServiceTest {
    @Test
    public void run(){
				//修改
        Whitebox.setInternalState(PersonService.class,"num",5);
				//访问
        int num=Whitebox.getInternalState(PersonService.class,"num");
        System.out.println(num);
    }
}

Whitebox 提供了很多 API,可以查看https://www.javadoc.io/doc/org.powermock/powermock-reflect/1.6.4/org/powermock/reflect/Whitebox.html

mock 单例模式

@PrepareForTest({SingleInstance.class})
@RunWith(PowerMockRunner.class)
public class PersonServiceTest {
    @Mock
    private SingleInstance singleInstance;

    @Test
    public void runSingle(){
        mockStatic(SingleInstance.class);
				// 获取单例模式的实例方法
        when(SingleInstance.getInstance()).thenReturn(singleInstance);
        when(singleInstance.test()).thenReturn("not find");
        PersonService personService=new PersonService();
        personService.testSingle();
    }
}

mock void方法

import static org.powermock.api.mockito.PowerMockito.*;
@RunWith(PowerMockRunner.class)
public class PersonServiceTest {
    @Mock
    private PersonDao personDao;

    @Test
    public void runVoid(){
        PersonService personService=new PersonService();
        doNothing().when(personDao).testVoid();
        personService.testVoid();
    }
}

capture

capture 用来捕获被调用时传入方法的参数值,该方法需要时被 mock 的方法。

import static org.powermock.api.mockito.PowerMockito.*;
@RunWith(PowerMockRunner.class)
public class PersonServiceTest {
    @Mock
    private PersonDao personDao;
		//声明captor类型
    @Captor
    private ArgumentCaptor<String> captor;

    @Test
    public void runCapture(){
        PersonService personService=new PersonService();
				//将mock出来的对象注入PersonService实例
        Whitebox.setInternalState(personService,"personDao",personDao);
				//先执行方法
        personService.test();
				//使用captor捕获变量
        Mockito.verify(personDao).test(captor.capture());
				//打印变量
        System.out.println(captor.getValue());
    }
}

验证

执行方法之后,需要验证方法执行是否符合预期结果,一般使用 Assert.assertEquals() 来验证*。*

import static org.powermock.api.mockito.PowerMockito.*;
@RunWith(PowerMockRunner.class)
public class PersonServiceTest {
    @Mock
    private PersonDao personDao;
    
    @Test
    public void runVerify(){
        PersonService personService=new PersonService();
        int str=personService.testVerify();
				//如果结果不为1,则运行失败
        Assert.assertEquals(1,str);
    }
}

总结

mock 能帮助我们解决很多依赖构建问题,同时提供的大量 API 能帮助我们方便测试极端情况的发生。但是随着测试用例不断增长,测试类也会越来越复杂,我们在编写时也要注意测试用例的维护。

其他

  1. 如何 mock 时,报错 Could not initialize plugin: interface org.mockito.plugins.MockMaker (alternate: null),需要引入依赖

    <dependency>
        <groupId>net.bytebuddy</groupId>
        <artifactId>byte-buddy</artifactId>
        <version>1.12.10</version>
    </dependency>
    
  2. mock 要在执行真实方法之前,不然会报 null,而 verify 验证跟 captor 捕获一般在真实方法调用后执行。

  3. 当方法的两个参数类型相同时,也可能报错:

    org.mockito.exceptions.misusing.InvalidUseOfMatchersException: Invalid use of argument matchers! 2 matchers expected, 1 recorded:
    

    解决办法为:都换成具体参数。