Mockito学习(1) --- 快速入门

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);
// 1
System.out.println(mockedList.size());
// 2
System.out.println(mockedList.size());
// 3
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的相同点和区别:

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

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

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

5、断言

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");
posted @ 2020-07-25 16:27  MoonGeek  阅读(2714)  评论(0编辑  收藏  举报