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对象方法的返回值了。
4、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的方法只能返回默认值,并且类的方法不会真正的执行。
3、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));
Spy
和Mock
的相同点和区别:
-
得到的对象同样可以进行“监管”,即验证和打桩。
-
如果不对spy对象的methodA打桩,那么调用spy对象的methodA时,会调用真实方法。
-
如果不对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");