Mockito是一款提供模拟测试的工具包,提供了对java单元测试对象的模拟、断言等等能力。
使用Mockeito可以为我们的单元测试提供一个模拟的无依赖的测试环境。让我们在单元测试的时候只关注当前的业务代码而不用考虑依赖项对主流程的影响。
Mockito的使用很简单,直接配合Junit使用即可,SpringBootTest starter集成了Mockito和Junit,如果单独使用记得引人Junit和Mockito依赖。
Mockito的使用,在进行测试的时候更具需要使用就好了,学习的话可以看官方提供的问题,因为Mockito使用的行为驱动模式,所以直接看API来的更快。
注意:这里需要导入Mockito、SpringTest、Junit、lombok的依赖
package com.lfsfenior.rocketmq;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.*;
@RunWith(SpringJUnit4ClassRunner.class)
public class MockioTest {
/* 使用@InjectMocks会自动注入被@Mock和@Spy注解的对象 */
@InjectMocks
private MockTcCls mockTcCls;
/* 使用@Mock注解创建完全模拟对象 */
@Mock
public QueryProductDetailService queryProductDetailService;
/* 使用@Spy创建中间对象,对于非指定模拟的方法,将调用实例的真实方法 */
@Spy
public TcProductService tcProductService;
@Before
public void init() throws Exception {
/* 为Mock对象指定行为 */
given(mockTcCls.queryProductDetailService.queryProductDetail(any())).willReturn("模拟返回");
/* 为Spy对象定制返回结果 */
when(mockTcCls.tcProductService.isOldOrNew(any())).thenAnswer((mock) -> {
String argument = mock.getArgument(0);
if (argument.startsWith("PD")) {
return "模拟返回";
}
return mock.callRealMethod();
});
}
@Test
public void mockito() throws Exception {
System.out.println(mockTcCls.queryProductDetailService.queryProductDetail("张三"));
System.out.println(mockTcCls.tcProductService.isOldOrNew("PD1234"));
System.out.println(mockTcCls.tcProductService.isOldOrNew("XLS1234"));
}
}
@AllArgsConstructor
@Setter
@Getter
class MockTcCls {
/* 使用@Mock注解创建完全模拟对象 */
@Mock
public QueryProductDetailService queryProductDetailService;
/* 使用@Spy创建中间对象,对于非指定模拟的方法,将调用实例的真实方法 */
@Spy
public TcProductService tcProductService;
}
class QueryProductDetailService {
public String queryProductDetail(String name) {
return "当前类返回";
}
}
class TcProductService {
public String isOldOrNew(String prd) {
return "当前类返回";
}
}
从源码我们可以看到对于@InjectMocks的处理,整体逻辑还是比较简单的,只是代码比较绕,跟一遍代码就好了。
其次我们知道Mockito的容器里对象就是最外层注册的对象,然后使用Contract注入的方式注入的,如果没有则会采用Properties注入方式的。
对于class解析,获取需要进行解析的对象以及需要虚拟的属性
public void injectMocks(final Object testClassInstance) {
Class<?> clazz = testClassInstance.getClass();
Set<Field> mockDependentFields = new HashSet<Field>();
Set<Object> mocks = newMockSafeHashSet();
while (clazz != Object.class) {
// 获取被@InjectMocks注入的对象
new InjectMocksScanner(clazz).addTo(mockDependentFields);
// 获取被@InjectMocks注入的对象中被@Mock和@Spy注解的属性
new MockScanner(testClassInstance, clazz).addPreparedMocks(mocks);
// 什么也没做就是留空的
onInjection(testClassInstance, clazz, mockDependentFields, mocks);
clazz = clazz.getSuperclass();
}
// 开始处理
new DefaultInjectionEngine().injectMocksOnFields(mockDependentFields, mocks, testClassInstance);
}
从源码我们知道,首先创建了OngoingMockInjection对象,并绑定必要的属性值。然后链式的绑定了三种注入策略,ConstructorInjection、PropertyOrFieldInjection、SpyOnInjectedFieldsHandler最核心的便是apply()方法了。
其次注入策略采用了责任链的方式。
public void injectMocksOnFields(Set<Field> needingInjection, Set<Object> mocks, Object testClassInstance) {
MockInjection.onFields(needingInjection, testClassInstance)
.withMocks(mocks)
// 加载构造函数注入方式
.tryConstructorInjection()
// 加载属性注入方式
.tryPropertyOrFieldInjection()
// 加载对@Spy的注入
.handleSpyAnnotation()
.apply();
}
public void apply() {
for (Field field : fields) {
// 构造器注入、属性注入
injectionStrategies.process(field, fieldOwner, mocks);
// 处理@Spy的注入策略
postInjectionStrategies.process(field, fieldOwner, mocks);
}
}
这里有个注意事项就是,要使用这个策略,那么这个对象必须有一个包含所有参数的构造器。主要的目的是讲@InjectMocks属性类进行实例化的。
底层也很简单,就是利用反射来初始化对象的,并且这个对象的所有属性都是空值,也就是说最后需要通过PropertyAndSetterInjection来讲属性绑定的
构造器注入的核心就是他的实例化方法initialize()-acquireFieldInstance()
private FieldInitializationReport acquireFieldInstance() throws IllegalAccessException {
Object fieldInstance = field.get(fieldOwner);
if(fieldInstance != null) {
return new FieldInitializationReport(fieldInstance, false, false);
}
//实例化对象,如果我们有全参数的构造函数就可以是显示依赖注入了
return instantiator.instantiate();
}
// 实例化对象
public FieldInitializationReport instantiate() {
final AccessibilityChanger changer = new AccessibilityChanger();
Constructor<?> constructor = null;
try {
constructor = biggestConstructor(field.getType());
changer.enableAccess(constructor);
// 获取绑定用的参数,来源就是初始化存入容器的对象
final Object[] args = argResolver.resolveTypeInstances(constructor.getParameterTypes());
Object newFieldInstance = constructor.newInstance(args);
setField(testClass, field,newFieldInstance);
return new FieldInitializationReport(field.get(testClass), false, true);
} catch (IllegalArgumentException e) {
throw new MockitoException("internal error : argResolver provided incorrect types for constructor " + constructor + " of type " + field.getType().getSimpleName(), e);
} catch (InvocationTargetException e) {
throw new MockitoException("the constructor of type '" + field.getType().getSimpleName() + "' has raised an exception (see the stack trace for cause): " + e.getTargetException().toString(), e);
} catch (InstantiationException e) {
throw new MockitoException("InstantiationException (see the stack trace for cause): " + e.toString(), e);
} catch (IllegalAccessException e) {
throw new MockitoException("IllegalAccessException (see the stack trace for cause): " + e.toString(), e);
} finally {
if(constructor != null) {
changer.safelyDisableAccess(constructor);
}
}
}
这个就是就是自动注入@Mock和@Spy对象了,走到最后你会发现,为null,为什么?
因为注入的前提是你有才能注入,没有我注入什么呢?对吧。
所以注入的前提是容器里得有这个对象才能注入到@InjectMocks注解的对象中
通过翻阅入口代码mockCandidates实际来源是初始化的时候存入的测试类的被@Mock和@Spy注解的属性,mocks
// 通过扫描当前测试类中被@Spy和@Mock注解的类,加入到全局容器mocks对象中,方便@InjectMocks注入依赖
public void addPreparedMocks(Set<Object> mocks) {
mocks.addAll(scan());
}
public boolean processInjection(Field injectMocksField, Object injectMocksFieldOwner, Set<Object> mockCandidates) {
FieldInitializationReport report = initializeInjectMocksField(injectMocksField, injectMocksFieldOwner);
// for each field in the class hierarchy
boolean injectionOccurred = false;
Class<?> fieldClass = report.fieldClass();
Object fieldInstanceNeedingInjection = report.fieldInstance();
while (fieldClass != Object.class) {
injectionOccurred |= injectMockCandidates(fieldClass, fieldInstanceNeedingInjection, newMockSafeHashSet(mockCandidates));
fieldClass = fieldClass.getSuperclass();
}
return injectionOccurred;
}
// 然后就是简单的过滤然注入到对象中
private boolean injectMockCandidatesOnFields(Set<Object> mocks,
Object injectee,
boolean injectionOccurred,
List<Field> orderedCandidateInjecteeFields) {
for (Iterator<Field> it = orderedCandidateInjecteeFields.iterator(); it.hasNext(); ) {
Field candidateField = it.next();
Object injected = mockCandidateFilter.filterCandidate(mocks, candidateField, orderedCandidateInjecteeFields, injectee)
.thenInject();
if (injected != null) {
injectionOccurred |= true;
mocks.remove(injected);
it.remove();
}
}
return injectionOccurred;
}
// 这个处理就比较简单了
protected boolean processInjection(Field field, Object fieldOwner, Set<Object> mockCandidates) {
FieldReader fieldReader = new FieldReader(fieldOwner, field);
// TODO refoctor : code duplicated in SpyAnnotationEngine
if(!fieldReader.isNull() && field.isAnnotationPresent(Spy.class)) {
try {
Object instance = fieldReader.read();
if (MockUtil.isMock(instance)) {
// A. instance has been spied earlier
// B. protect against multiple use of MockitoAnnotations.initMocks()
Mockito.reset(instance);
} else {
Object mock = Mockito.mock(instance.getClass(), withSettings()
.spiedInstance(instance)
.defaultAnswer(Mockito.CALLS_REAL_METHODS)
.name(field.getName()));
setField(fieldOwner, field, mock);
}
} catch (Exception e) {
throw new MockitoException("Problems initiating spied field " + field.getName(), e);
}
}
return false;
}
Mockito