前言
学习B站汪文君Mockito实战视频的笔记
1 mock的三种方式
1.1 MockByRunner
第一种mock方式,这种方式的mock的对象可以是测试函数的局部变量
package com.lanshang.mockito.lesson3;
import com.lanshang.mockito.quickstart.AccountDao;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
//第一种mock方式,这种方式的mock的对象可以是测试函数的局部变量
@RunWith(MockitoJUnitRunner.class)
public class MokeByRunner {
@Test
public void test(){
AccountDao dao = Mockito.mock(AccountDao.class);
}
}
1.2 MockByAnnotation
第二种mock方式,注释的方式,这种方式mock的对象不可以是测试函数的局部变量,所以这里把需要mock的对象提到测试类的成员变量
package com.lanshang.mockito.lesson3;
import com.lanshang.mockito.quickstart.AccountDao;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
//第二种mock方式,注释的方式,这种方式mock的对象不可以是测试函数的局部变量,所以这里把需要mock的对象提到测试类的成员变量
public class MockByAnnotation {
@Mock
AccountDao accountDao;
@Before
public void init(){
MockitoAnnotations.initMocks(this);
}
@Test
public void test(){
accountDao.findAccount("x","x");
}
}
1.3 MockByRule
注意:这种mock方法在高版本可用,在1.9.0的mockito-all不支持
package com.lanshang.mockito.lesson3;
import com.lanshang.mockito.quickstart.AccountDao;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
//注意:这种mock方法在高版本可用,在1.9.0的mockito-all不支持
public class MockByRule {
@Mock
AccountDao accountDao;
@Rule
public MockitoRule mockitorule = MockitoJUnit.rule();
@Test
public void test(){
accountDao.findAccount("x","x");
}
}
2 深度mock
注意:深度Moke并不能确保每次都生效.
使用方法:在需要mock的对象注释上头这么写: @Mock(answer = Answers.RETURNS_DEEP_STUBS)
package com.lanshang.mockito.lesson3;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
public class DeepMode {
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
A a;
@Before
public void init(){
MockitoAnnotations.initMocks(this);
}
@Test
public void test(){
a.getB().a();
}
}
class A{
public B getB(){
throw new RuntimeException();
}
}
class B{
public void a(){
throw new RuntimeException();
}
}
3 Stub
3.1 有返回值的函数的测试方法
注意:list已经mock
@Test
public void testStubbing1(){
//测试有返回值的函数的正常情况
when(list.get(0)).thenReturn("0");
//上述写法与下面的写法等同
//doReturn("0").when(list).get(0);
assertThat(list.get(0),equalTo("0"));
//测试有返回值的函数的异常情况
when(list.get(1)).thenThrow(new RuntimeException());
try {
list.get(1);
fail();
} catch (Exception e) {
assertThat(e,instanceOf(RuntimeException.class));
}
}
3.2 无返回值的函数的测试方法
注意:list已经mock
//测试没有返回值的函数
@Test
public void testStubbing2(){
//测试无返回值的函数的正常情况
doNothing().when(list).clear();
list.clear();
verify(list,times(1)).clear();
//测试无返回值的函数的异常情况
doThrow(new RuntimeException()).when(list).clear();
try {
list.clear();
fail();
} catch (Exception e) {
assertThat(e,instanceOf(RuntimeException.class));
}
}
3.3 遍历stub
@Test
public void testIterateStubbing(){
//指定list.get(0)第一次调用结果为1,第二次调用结果为2,第三次调用结果为3
when(list.get(0)).thenReturn(1,2,3);
//下述写法等价于when(list.get(0)).thenReturn(1,2,3);
//when(list.get(0)).thenReturn(1).thenReturn(2).thenReturn(3);
assertThat(list.get(0),equalTo(1));
assertThat(list.get(0),equalTo(2));
assertThat(list.get(0),equalTo(3));
//第三次后调用结果重复第三次调用的值
assertThat(list.get(0),equalTo(3));
assertThat(list.get(0),equalTo(3));
}
3.4 when().thenAnswer()用法
when().thenAnswer()用于简化when里方法的每个参数要有同样的逻辑处理。
比如返回list.get()入参的10倍:
when(list.get(0)).thenReturn(0);
when(list.get(1)).thenReturn(10);
when(list.get(23)).thenReturn(230);
……
就可以写成以下方式:
@Test
public void testStubbingWithAnswer(){
when(list.get(anyInt())).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
//invocationOnMock.getArguments()获取list.get()函数所有的入参,
//因为list.get()函数只有一个入参,所以用invocationOnMock.getArguments()[0]取出该入参的值
Integer index = (Integer)invocationOnMock.getArguments()[0];
return index*10;
}
});
assertThat(list.get(0),equalTo(0));
assertThat(list.get(1),equalTo(10));
assertThat(list.get(23),equalTo(230));
}
3.5 when().thenCallRealMethod()用法
如果想mock出的对象真正执行其方法,可以用when().thenCallRealMethod()
class Service{
public Integer light(){
System.out.println("light()方法执行!");
return 999;
}
public String weight(){
System.out.println("weight()方法执行!");
return "weight……";
}
}
@Test
public void testRealCall(){
Service service = mock(Service.class);
//mock出的对象调用其方法不会真的执行该方法,而是返回其返回类型的默认值
//service.light()的返回类型是Integer,所以输出0
System.out.println(service.light());
//service.weight()的返回类型是String,所以输出null
System.out.println(service.weight());
//如果想mock出的对象真正执行其方法,可以用when().thenCallRealMethod()
when(service.light()).thenCallRealMethod();
assertThat(service.light(),equalTo(999));
//mock出的对象不真实调用其方法的写法和原来一样
when(service.weight()).thenReturn("suc");
assertThat(service.weight(),equalTo("suc"));
}
4 Spy
spy不做处理时会真正调用函数,stub后会不真正调用函数而是返回指定结果。
4.1 方法一:MockByRunner
@Test
public void test(){
ArrayList<String> realList = new ArrayList<>();
ArrayList<String> list = spy(realList);
list.add("A");
list.add("B");
assertThat(list.get(0),equalTo("A"));
assertThat(list.get(1),equalTo("B"));
assertThat(list.isEmpty(),equalTo(false));
assertThat(list.size(),equalTo(2));
//对部分方法进行stub使其不调用真实函数
when(list.isEmpty()).thenReturn(true);
when(list.size()).thenReturn(0);
assertThat(list.isEmpty(),equalTo(true));
assertThat(list.size(),equalTo(0));
}
4.2 MockByAnnotation
其实就是把
ArrayList<String> realList = new ArrayList<>();
ArrayList<String> list = spy(realList);
合并成一步:
@Spy
ArrayList<String> list = new ArrayList<>();
完整代码如下:
public class SpyAnnotation {
@Spy
ArrayList<String> list = new ArrayList<>();
@Before
public void init(){
MockitoAnnotations.initMocks(this);
}
@Test
public void test(){
list.add("A");
list.add("B");
assertThat(list.get(0),equalTo("A"));
assertThat(list.get(1),equalTo("B"));
assertThat(list.isEmpty(),equalTo(false));
assertThat(list.size(),equalTo(2));
//对部分方法进行stub使其不调用真实函数
when(list.isEmpty()).thenReturn(true);
when(list.size()).thenReturn(0);
assertThat(list.isEmpty(),equalTo(true));
assertThat(list.size(),equalTo(0));
}
}
5 isA()和any()
stub用isA()时,assert会校验类型;stub用any()时,assert不会校验类型。
package com.lanshang.mockito.lesson7;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import static javafx.beans.binding.Bindings.when;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.isA;
public class Args {
@Test
public void test(){
Foo foo = Mockito.mock(Foo.class);
Mockito.when(foo.get(isA(Parent.class))).thenReturn(100);
assertThat(foo.get(new Child1()),equalTo(100));
assertThat(foo.get(new Child2()),equalTo(100));
//any(Child1.class)不会真的校验Child1.class类型,只要assert里foo.get()的参数类型是对的就能成功
Mockito.when(foo.get(any(Child1.class))).thenReturn(300);
assertThat(foo.get(new Child2()),equalTo(300));
}
}
class Foo{
public int get(Parent parent){
return parent.fun();
}
}
interface Parent{
public int fun();
}
class Child1 implements Parent{
@Override
public int fun() {
throw new RuntimeException();
}
}
class Child2 implements Parent{
@Override
public int fun() {
throw new RuntimeException();
}
}
6 参数通配
待测试类
注意:SimpleService simpleService已经mock。
class SimpleService{
public int method1(int i, String s, Collection c, Serializable ser){
throw new RuntimeException();
}
public void method2(int i, String s, Collection c, Serializable ser){
throw new RuntimeException();
}
}
6.1 有返回值的函数
有返回值的函数用assert验证执行结果。
@Test
public void test1Method1(){
Mockito.when(simpleService.method1(anyInt(),anyString(),anyCollection(),anyObject()))
.thenReturn(100);
Mockito.when(simpleService.method1(anyInt(),eq("a"),anyCollection(),anyObject()))
.thenReturn(1);
Mockito.when(simpleService.method1(anyInt(),eq("b"),anyCollection(),anyObject()))
.thenReturn(2);
assertThat(simpleService.method1(1,"c", Collections.emptyList(),"aaaa"),equalTo(100));
assertThat(simpleService.method1(1,"b", Collections.emptyList(),"aaaa"),equalTo(2));
assertThat(simpleService.method1(1,"a", Collections.emptyList(),"aaaa"),equalTo(1));
}
注意:按下面这种顺序的mock,第三个mock会覆盖前两个mock
@Test
public void testMethod1(){
Mockito.when(simpleService.method1(anyInt(),eq("a"),anyCollection(),anyObject()))
.thenReturn(1);
Mockito.when(simpleService.method1(anyInt(),eq("b"),anyCollection(),anyObject()))
.thenReturn(2);
//注意,按这种顺序的mock,第三个mock会覆盖前两个mock
Mockito.when(simpleService.method1(anyInt(),anyString(),anyCollection(),anyObject()))
.thenReturn(100);
assertThat(simpleService.method1(1,"c", Collections.emptyList(),"aaaa"),equalTo(100));
assertThat(simpleService.method1(1,"b", Collections.emptyList(),"aaaa"),equalTo(100));
assertThat(simpleService.method1(1,"a", Collections.emptyList(),"aaaa"),equalTo(100));
}
6.2 无返回值的函数
无返回值的函数用verify验证执行结果。
@Test
public void test1Method2(){
Mockito.doNothing().when(simpleService).method2(anyInt(),anyString(),anyCollection(),isA(Serializable.class));
simpleService.method2(1,"a", Collections.emptyList(),"aaaa");
Mockito.verify(simpleService,times(1)).method2(1,"a", Collections.emptyList(),"aaaa");
}
7 assertThat一些用法
@Test
public void test(){
int i=10;
assertThat(i,equalTo(10));
assertThat(i,not(equalTo(20)));
assertThat(i,is(10));
assertThat(i,is(not(20)));
}
@Test
public void test2(){
int i=10;
assertThat(i,both(equalTo(10)).and(not(equalTo(20))));
assertThat(i,either(equalTo(10)).or(equalTo(20)));
assertThat(i,anyOf(is(10),is(20),not(30)));
assertThat(i,allOf(is(10),not(is(20)),not(30)));
assertThat(i,is(not(20)));
assertThat(Stream.of(1,2,3).anyMatch(j->j<2),equalTo(true));
assertThat(Stream.of(1,2,3).allMatch(j->j<5),equalTo(true));
}
8 注意点
8.1 不能mock局部变量
可以mock被测试方法所属类的成员变量,也可以moke被测试方法的入参,但是不能moke被测试方法的局部变量。moke被测试方法的局部变量会失效。
8.2 被测试的类中调用的函数的参数不需要mock
以测试类为基础,直接写在测试类的代码搞不到的就要mock,比如下面代码的这两行
String name = request.getParameter("name"); String password = request.getParameter("password");
package com.lanshang.mockito.quickstart;
import javax.servlet.http.HttpServletRequest;
public class AccountLoginController {
AccountDao accountDao;
public AccountLoginController(AccountDao accountDao) {
this.accountDao = accountDao;
}
public String login(HttpServletRequest request){
String name = request.getParameter("name");
String password = request.getParameter("password");
try {
Account account = accountDao.findAccount(name,password);
if(account == null){
return "/login";
}else{
return "/index";
}
} catch (Exception e) {
e.printStackTrace();
return "/505";
}
}
}
测试类
package com.lanshang.mockito.quickstart;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
import javax.servlet.http.HttpServletRequest;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsEqual.equalTo;
@RunWith(MockitoJUnitRunner.class)
public class AccountLoginControllerTest {
AccountDao dao ;
AccountLoginController accountLoginController ;
HttpServletRequest request;
Account account = new Account();
@Before
public void init(){
dao = Mockito.mock(AccountDao.class);
accountLoginController = new AccountLoginController(dao);
request = Mockito.mock(HttpServletRequest.class);
}
@Test
public void loginTest(){
Mockito.when(request.getParameter("name")).thenReturn("cat");
Mockito.when(request.getParameter("password")).thenReturn("123456");
Mockito.when(dao.findAccount(Mockito.anyString(),Mockito.anyString())).thenReturn(account);
String login = accountLoginController.login(request);
assertThat(login,equalTo("/index"));
}
}
但如果String name = request.getParameter("name"); String password = request.getParameter("password");
这两行代码没有单独写,而是作为被测试的代码中某个调用函数的入参,如下图: Account account = accountDao.findAccount(request.getParameter("name"),request.getParameter("password"));
则可以不用mock,测试到调用函数这一行时入参用any代替就好了
被测试代码:
package com.lanshang.mockito.quickstart;
import javax.servlet.http.HttpServletRequest;
public class AccountLoginController {
AccountDao accountDao;
public AccountLoginController(AccountDao accountDao) {
this.accountDao = accountDao;
}
public String login(HttpServletRequest request){
try {
Account account = accountDao.findAccount(request.getParameter("name"),request.getParameter("password"));
if(account == null){
return "/login";
}else{
return "/index";
}
} catch (Exception e) {
e.printStackTrace();
return "/505";
}
}
}
测试代码
package com.lanshang.mockito.quickstart;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
import javax.servlet.http.HttpServletRequest;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsEqual.equalTo;
@RunWith(MockitoJUnitRunner.class)
public class AccountLoginControllerTest {
AccountDao dao ;
AccountLoginController accountLoginController ;
HttpServletRequest request;
Account account = new Account();
@Before
public void init(){
dao = Mockito.mock(AccountDao.class);
accountLoginController = new AccountLoginController(dao);
request = Mockito.mock(HttpServletRequest.class);
}
@Test
public void loginTest(){
Mockito.when(dao.findAccount(Mockito.anyString(),Mockito.anyString())).thenReturn(account);
String login = accountLoginController.login(request);
assertThat(login,equalTo("/index"));
}
}
8.3 mock出的对象最好reset
mock出的对象最好reset下,在测试结束后清除stub造成的影响。下例simpleService是mock的对象。
@After
public void destroy(){
Mockito.reset(simpleService);
}