Mock的基本原理
通过生成子类的方式,拦截具体的方法调用,将方法调用转发至Mock的桩代码,从而实现方法返回值或者方法执行体的自定义。
Mock和Spy
区别
Mock和Spy的基本区别——在没有进行任何方法调用Mock的情况下,Mock根据全局配置根据方法调用返回值类型返回一个默认值,不会进行具体的方法调用;而Spy对象会默认调用真实的方法。
调用方法的行为
场景 | Spy | Mock |
---|---|---|
普通类 | 调用真实的方法 | 根据调用方法返回值类型直接返回默认值 |
抽象类 | 对于非抽象方法,调用真实的方法;对于抽象方法,根据调用方法返回值类型直接返回默认值 | 任何类型的方法都是根据调用方法返回值类型直接返回默认值 |
接口 | 根据调用方法返回值类型直接返回默认值 | 根据调用方法返回值类型直接返回默认值 |
使用场景实例
/**
* @author lidelin.
*/
public class WithDepTest {
/**
* 使用了Spy.
* 因为需要对同一个对象同时进行方法测试和方法Mock.
**/
@Spy
@InjectMocks
private WithDepService withDepService;
/**
* 使用了Mock.
* 因为不需要关注方法逻辑,所有方法都是需要被Mock的.
**/
@Mock
private IOtherService iOtherService;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testMethod1() {
// 进行接口对象方法的Mock
Mockito.doAnswer(inv -> null).when(iOtherService).service();
withDepService.method1();
}
@Test
public void testMethod0() {
// 因为依赖method1,而method1在上面已经进行了单测,所以直接进行具体场景的mock
Mockito.doAnswer(inv -> null).when(withDepService).method1();
// method0还是进行的实际的方法调用
// 但是涉及到method1的调用都会被拦截
withDepService.method0();
}
public static class WithDepService {
private IOtherService service;
public void method0() {
method1();
}
public void method1() {
// do something.
service.service();
}
}
public interface IOtherService {
void service();
}
}
Stubbing
插桩顺序的问题
给出一个场景(该场景仅存在于使用 @Spy
mock对象的时候)
/**
* @author lidelin.
*/
public class SpyTest2 {
@Spy
private TestLogic logic;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
@Test
public void test() {
// NPE
Mockito.when(logic.needMock(Mockito.any())).thenReturn(0);
// 正常
Mockito.doReturn(0).when(logic).needMock(Mockito.any());
}
public static class TestLogic {
public int needMock(String a) {
return a.length();
}
}
}
这个场景可以抽象成下面两种插桩方式的区别:
// 插桩先行
Mockito.doAnswer(answer).when(mock).callMethod(ArgMatcher...);
// 插桩后行
Mockito.when(mock.callMethod(ArgMatcher...)).doAnswer(answer);
插桩先行
从执行流程图可以看出,在插桩先行的场景中,不管是@Spy
还是@Mock
,在实际Mock的过程中都不会进行实际方法的调用。
插桩后行
从执行流程图可以看出,在插桩后行的场景中,@Spy
在Mock的过程中都会进行实际方法的调用,由于Mockito.any()
会默认传入一个null
值的参数,该参数会被传入到实际的方法调用中从而造成NPE。
总结
1、Mockito的基本原理就是使用方法拦截器,判断每次方法调用的状态来对方法进行相应的操作
2、@Spy
和@Mock
的本质区别就是在未进行插桩之前,默认操作是否会进行实际方法的调用
3、需要注意插桩顺序,防止业务代码逻辑导致Mock过程出现错误