添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

Mockito是一个模拟测试框架,可以让你用优雅,简洁的接口写出漂亮的单元测试。Mockito可以让单元测试易于可读,产生简洁的校验错误。

1、如何使用Mockito

引入mavne依赖

<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-core</artifactId>
  <version>2.23.4</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <scope>test</scope>
</dependency>

有三种方法可以配置使用 Mockito

MockitoJUnitRunner 注解

接下来主要用这种方法来介绍

import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.Mock;
// 测试类加上 @RunWith(MockitoJUnitRunner.class) 注解
@RunWith(MockitoJUnitRunner.class)
public class MockByRunnerTest {
    @Mock
    private AccountDao accountDao;

MockitoAnnotations 方式

import org.mockito.MockitoAnnotations;
import org.mockito.Mock;
import org.junit.Before;
public class MockByAnnotationTest {
    @Mock
    private AccountDao accountDao;
    @Before
    public void init(){
      	// 初始化
        MockitoAnnotations.initMocks(this);

@Rule 注解

import org.junit.Rule;
import org.mockito.Mock;
public class MockByRuleTest {
  	// 初始化
    @Rule
    public MockitoRule mockitoRule = MockitoJUnit.rule();
    @Mock
    AccountDao accountDao;

2、什么是Mock测试

mock测试就是在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法。

比如一个类A,有B、C、D等多个复杂类的成员变量。如果我们测试时候通过new A()的方式来创建A对象,就需要同时手动创建B、C、D三个对象,并和A关联,提高了测试的复杂性。Mock可以自动生成一个虚拟的A对象,就是帮我们省去了这些复杂的创建流程。

Mockito提供了两种Mock的方式:

import org.mockito.Mock;
import static org.mockito.Mockito.mock;
@RunWith(MockitoJUnitRunner.class)
public class MockByRunnerTest2 {
    // 使用@Mock注解
    @Mock
    private AccountDao accountDao;
    @Test
    public void test1() {
        Account account = accountDao.findById(1);
      	// 返回null
        System.out.println(account;
        // 输出 class com.sanyue.learn.mockito.mockitodemo.domain.Account$MockitoMock$1675715420
        System.out.println(account.getClass());          
    public void test2() {
        // 使用mock方法
        AccountDao accountDao = mock(AccountDao.class, Mockito.RETURNS_SMART_NULLS);
        Account account = accountDao.findById(1);
        // 返回null
      	System.out.println(accoun;

Mock生成的是一个代理对象,默认情况下,执行对象的所有的方法都返回该方法的返回类型的默认值,不会真正去执行该对象的方法。既然这样,那我们在测试中如何使用这个mock出来的对象,来执行方法进行测试呢?这就需要使用到Mockito的Stub(测试桩)来设置Mock对象方法的返回值了。

3、Stub(测试桩) 介绍

上面介绍了Mock生成的对象,其实是一个代理对象,不会真正去执行类里面的方法。为了便于我们测试,我们需要用到Stub来设置我们期望的方法返回值,可以理解为创建测试用例

// 你可以mock具体的类型,不仅只是接口
LinkedList mockedList = mock(LinkedList.class);
// 开始设置测试桩
// 当get(0)被调用时,返回"first"
when(mockedList.get(0)).thenReturn("first");
// 方法get(1)被调用时,抛异常。
when(mockedList.get(1)).thenThrow(new RuntimeException());
// 输出 "first"
System.out.println(mockedList.get(0));
// 抛出异常
System.out.println(mockedList.get(1));
// 输出 null,因为get(999)的调用没有被设置过
System.out.println(mockedList.get(999));

由上面例子可以看到,Stub就是人为指定 当使用该参数调用该方法时,方法返回什么值。下面介绍一些其他的Stud方式:

 // 使用doReturn语句和when语句一样的效果
 doReturn(1).when(mockedList).get(1);
 // 输出 1
 System.out.println(mockedList.get(1));
 // 使用doNothing来设置void返回值的方法
 doNothing().when(mockedList).clear();
 // 设置执行clear方法抛出异常
 doThrow(new RuntimeException()).when(mockedList).clear();
 mockedList.clear();
 // 以下断言表示,mockedList的clear方法被调用了1次
 verify(mockedList, times(1)).clear();

设置每次调用返回不同的值

如果希望每次调用的返回值都不一样可以这样设置:

// 第1次调用返回2,第2次返回2,以后再调用返回3
when(mockedList.size()).thenReturn(1, 2, 3);
// 等价写法
// when(mockedList.size()).thenReturn(1).thenReturn(2).thenReturn(3).thenReturn(4);
System.out.println(mockedList.size());
System.out.println(mockedList.size());
System.out.println(mockedList.size());
// 超过3次后调用,也返回3
System.out.println(mockedList.size());

也可以通过thenAnswer方式来设置不同调用次数返回不同的值:

// 设置返回值是 参数值*10
when(list.get(anyInt())).thenAnswer(new Answer(){
    @Override
    public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
      int arguments = invocationOnMock.getArgument(0);
      return 10*arguments;

参数匹配器

设置用参数匹配器根据不同类型参数,返回不同的值:

 public class TestService {
   // 定义一个方法
   public String say(String param1, Integer param2, String param3) {
     return "hello";
@Test
public void test3(){
  TestService testService = mock(TestService.class);
  // anyString() 表示任何字符串参数,anyInt() 表示任何int类型参数
  when(testService.say(anyString(), anyInt(), anyString())).thenReturn("world");
  // 输出 world
  System.out.println(testService.say("x", 1, "x"));
  // 如果参数列表包含参数匹配器,则必能出现具体参数值,要使用eq() 方法代替
  // when(testService.say(anyString(), 1, anyString())).thenReturn("world2");
  when(testService.say(anyString(), eq(1), anyString())).thenReturn("world2");
  // 输出 world2
  System.out.println(testService.say("x", 1, "x"));

设置执行真实的方法

可以使用thenCallRealMethod来设置执行对象真正的方法

 List list = mock(LinkedList.class);
 when(list.size()).thenCallRealMethod();

重置Mock对象

使用reset方法可以重置Mock对象Stub的设置

List mock = mock(List.class);
when(mock.size()).thenReturn(10);
mock.add(1);
reset(mock);

do系列方法的运用

当你调用doThrow(), doAnswer(), doNothing(), doReturn() and doCallRealMethod() 这些函数时可以在适当的位置调用when()函数. 当你需要下面这些功能时这是必须的:

  • 测试void函数
  • 在受监控的对象上测试函数
  • 不知一次的测试为同一个函数,在测试过程中改变mock对象的行为,比如为Spy对象进行Stub
  • anyObject(), eq()这样的匹配器函数不会返回匹配器。它们会在内部将匹配器记录到一个栈当中,并且返回一个假的值,通常为null。这样的实现是由于被Java编译器强加的静态类型安全。结果就是你不能在验证或者测试桩函数之外使用anyObject(), eq()函数。

    如果一个方法没有被Stub设置,会返回该方法返回类型的默认值,比如int类型返回0,boolean返回false,对象类型返回null。

    需要记住的是 mock对象会覆盖整个被mock的对象,因此没有stub的方法只能返回默认值,并且类的方法不会真正的执行。

    4、Spy 介绍

    Mock出来的对象(代理对象),默认不会去真正执行类的方法。而用Spy声明的对象(真实对象),则会默认执行真正的方法。

    * 也可以使用@Spy注解方式初始化spy对象 * @Spy * List<Integer> list = new ArrayList<>(); List<Integer> realList = new ArrayList<>(); List<Integer> list = spy(realList); list.add(1); list.add(2); // 分别输出1和2,说明真正执行了add和get方法 System.out.println(list.get(0)); System.out.println(list.get(1)); // 进行部分mock when(list.isEmpty()).thenReturn(true); // 输出true,说明isEmpty方法被mock了 System.out.println(list.isEmpty()); // 分别输出1和2,说明get方法不受mock影响 System.out.println(list.get(0)); System.out.println(list.get(1));

    需要注意的是,如果为Spy出来的对象进行Stub,有时候不能使用when,因为Spy对象调用方法时,会调用真实的方法。比如以下例子:

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

    SpyMock的相同点和区别:

    得到的对象同样可以进行“监管”,即验证和打桩。

    如果不对spy对象的methodA打桩,那么调用spy对象的methodA时,会调用真实方法。

    如果不对mock对象的methodA打桩,将doNothing,且返回默认值(null,0,false)。

    Mockito中断言的使用和Junit的一样,这里举几个例子,不详细描述:

    List<Integer> list = mock(List.class);
    // 断言list.get(0)值等于1
    assertThat(list.get(0), equalTo(1));
    // 断言大于50
    assertThat(list.get(0), greaterThan(20));
    // 断言小于等于50
    assertThat(list.get(0), lessThanOrEqualTo(50));
    // 断言 必须大于20 并且 小于等于50(所有条件成立)
    assertThat(list.get(0), allOf(greaterThan(20), lessThanOrEqualTo(50)));
    // 断言 必须大于20 或 小于等于50(其中至少一个条件成立)
    assertThat(list.get(0), oneOf(greaterThan(20), lessThanOrEqualTo(50)));
    // 断言任何条件都成立
    assertThat(list.get(0), anything());
    // 断言等于1
    assertThat(list.get(0), is(1));
    // 断言不等于-1
    assertThat(list.get(0), not(-1));
    // 断言返回的字符串包含1
    assertThat(list.get(0), containsString("1"));
    // 断言返回的字符串以1开头
    assertThat(list.get(0), startsWith("1"));
    // 断言该异常属于RuntimeException
    assertThat(e, instanceOf(RuntimeException.class));
    

    可以这样断言异常

    try {
    	list.clear();
    	// 如果执行到这一步,返回失败
    	fail();
    } catch (Exception e) {
    	assertThat(e, instanceOf(RuntimeException.class));
    

    6、验证函数的调用次数

    Mockito可以对函数的执行过程进行断言,通过断言函数的执行次数,要对方法执行逻辑进行判断。

    List<Integer> mockedList = mock(List.class);
     mockedList.add("once");
     mockedList.add("twice");
     mockedList.add("twice");
     mockedList.add("three times");
     mockedList.add("three times");
     mockedList.add("three times");
     // 下面的两个验证函数效果一样,期望mockedList的add("once")方法执行了1次
     verify(mockedList).add("once");
     verify(mockedList, times(1)).add("once");
     // 验证具体的执行次数,分别希望是2次和3次
     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");
     verify(mockedList, atLeast(2)).add("five times");
     verify(mockedList, atMost(5)).add("three times");
    

    7、验证方法的执行顺序

    可以使用InOrder来对方法的执行顺序进行验证

     // 进行mock
    List singleMock = mock(List.class);
    singleMock.add("was added first");
    singleMock.add("was added second");
    // 为该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");