当我们 mock 一个方法, Mockito 帮我们做了什么
@Test
public void testGetUserById() {
// 准备测试数据
Long id = 1L;
User mockUser1 = new User();
mockUser1.setId(1L);
mockUser1.setName("mockUser1");
User mockUser2 = new User();
mockUser2.setId(2L);
mockUser2.setName("mockUser2");
// 调用业务方法
User result = userService.findUserById(id);
System.out.println(result);
// 定义 mock 对象的行为
Mockito.when(userService.findUserById(id)).thenReturn(mockUser1);
Mockito.when(userService.findUserById(id)).thenReturn(mockUser2);
// 调用业务方法并验证 mock 对象的行为是否符合预期
result = userService.findUserById(id); // 返回 mockUser2,因为后定义的 stubbing 覆盖了前面的 stubbing
System.out.println(result); // 打印结果为 mockUser2
result = userService.findUserById(id); // 返回 mockUser2,因为没有新的 stubbing 覆盖之前的 stubbing
System.out.println(result); // 打印结果为 mockUser2
}
这个测试方法主要测试了使用 Mockito 框架来 mock 一个 userService 对象,定义了两个 stubbing,然后调用业务方法并验证 mock 对象的行为是否符合预期。需要注意的是,后定义的 stubbing 会覆盖之前的 stubbing。
运行结果:
null
User{id=2, name='mockUser2', email='null'}
User{id=2, name='mockUser2', email='null'}
Mockito 在定义 stubbing 的时候,后定义的 stubbing 会覆盖之前的 stubbing。这是因为 Mockito 在内部使用了一个策略来管理 stubbing,它会按照 stubbing 定义的顺序依次查找匹配的答案。当后面的 stubbing 覆盖了前面的 stubbing 时,Mockito 会自动选择覆盖的 stubbing,这样就可以保证 mock 对象的行为符合预期。
如果需要定义多个 stubbing 并让它们依次生效,可以使用 Mockito 提供的连续 stubbing 的方式,即使用 thenReturn() 方法返回多个值。例如:
Mockito.when(mockObject.someMethod()).thenReturn("foo", "bar", "baz");
这样,每次调用 someMethod() 方法时,会依次返回 “foo”、“bar” 和 “baz” 三个值。
在springboot测试中, 我们mock某一个方法, 底层到底做了啥…
Mockito.when(userService.findUserById(id)).thenReturn(mockUser1);
我们知道 mock 对象是通过代理执行拦截器里面的方法的
但是底层究竟做了什么, 让我们可以得到我们的行为
userService.findUserById(id)
被拦截器拦截, 将该调用方法拦截, 封装成 Invocation 方法全局变量
如果有同名那就覆盖…
咱就是说不能用链表存起来吗…
org.mockito.internal.creation.bytebuddy.MockMethodInterceptor
Object doIntercept(
Object mock,
Method invokedMethod,
Object[] arguments,
RealMethod realMethod,
Location location)
throws Throwable {
// 为了避免 JVM JIT 在方法执行期间对 mock 实例进行垃圾回收,我们使用一个线程本地变量将其逃逸出去。
// 这样可以确保在 handler.handle(...) 方法完成前,mock 实例不会被回收。
weakReferenceHatch.set(mock);
try {
// 调用 handler 的 handle 方法,处理 mock 对象的方法调用,并返回结果
return handler.handle(
createInvocation(
mock,
invokedMethod,
arguments,
realMethod,
mockCreationSettings,
location));
} finally {
// 移除线程本地变量中的 mock 实例
weakReferenceHatch.remove();
}
}
这个方法是 Mockito 框架中的一个重要方法,用于拦截 mock 对象的方法调用,并调用 handler 的 handle 方法处理方法调用并返回结果。具体作用是:
-
为了避免 JVM JIT 在方法执行期间对 mock 实例进行垃圾回收,我们使用一个线程本地变量将其逃逸出去。这样可以确保在 handler.handle(…) 方法完成前,mock 实例不会被回收。
-
调用 handler 的 handle 方法,处理 mock 对象的方法调用,并返回结果。
-
移除线程本地变量中的 mock 实例。
通过这个方法,Mockito 可以拦截 mock 对象的方法调用,并调用 handler 的 handle 方法处理方法调用并返回结果,以便在测试中模拟真实的对象行为。
org.mockito.internal.handler.MockHandlerImpl
public Object handle(Invocation invocation) throws Throwable {
// 如果存在可用于 stubbing 的答案,则使用答案来 stub 方法调用
if (invocationContainer.hasAnswersForStubbing()) {
InvocationMatcher invocationMatcher =
matchersBinder.bindMatchers(
mockingProgress().getArgumentMatcherStorage(), invocation);
invocationContainer.setMethodForStubbing(invocationMatcher);
return null;
}
// 获取当前的 verificationMode
VerificationMode verificationMode = mockingProgress().pullVerificationMode();
// 绑定 invocationMatcher 到调用参数上
InvocationMatcher invocationMatcher =
matchersBinder.bindMatchers(
mockingProgress().getArgumentMatcherStorage(), invocation);
// 验证当前 mockingProgress 的状态
mockingProgress().validateState();
// 如果 verificationMode 不为 null,则调用了 verify() 方法,并需要进行验证
if (verificationMode != null) {
// 需要检查是否在正确的 mock 上进行了 verify()
if (((MockAwareVerificationMode) verificationMode).getMock() == invocation.getMock()) {
VerificationDataImpl data =
new VerificationDataImpl(invocationContainer, invocationMatcher);
verificationMode.verify(data);
return null;
} else {
// 如果在不同的 mock 上进行了 verify(),则需要重新添加 verificationMode
mockingProgress().verificationStarted(verificationMode);
}
}
// 准备 invocationMatcher 用于 stubbing
invocationContainer.setInvocationForPotentialStubbing(invocationMatcher);
OngoingStubbingImpl<T> ongoingStubbing = new OngoingStubbingImpl<T>(invocationContainer);
mockingProgress().reportOngoingStubbing(ongoingStubbing);
// 查找当前 invocation 的答案
StubbedInvocationMatcher stubbing = invocationContainer.findAnswerFor(invocation);
// TODO #793 - when completed, we should be able to get rid of the casting below
notifyStubbedAnswerLookup(
invocation,
stubbing,
invocationContainer.getStubbingsAscending(),
(CreationSettings) mockSettings);
if (stubbing != null) {
// 将调用参数捕获到 stubbing 中
stubbing.captureArgumentsFrom(invocation);
try {
// 使用 stubbing 的答案来处理方法调用,并返回结果
return stubbing.answer(invocation);
} finally {
// 需要重新报告 ongoingStubbing,以便在某些场景下正确隔离 stubbing
mockingProgress().reportOngoingStubbing(ongoingStubbing);
}
} else {
// 使用 defaultAnswer 来处理方法调用,并返回结果
Object ret = mockSettings.getDefaultAnswer().answer(invocation);
DefaultAnswerValidator.validateReturnValueFor(invocation, ret);
// Mockito 使用它来在部分模拟/间谍的情况下重新设置 invocationForPotentialStubbing 的调用,否则 'when' 中的真实方法可能会委托给其他 self 方法,并使用不同的方法重写预期的 stubbed 方法。
// 这意味着我们将使用错误的方法进行 stubbing。
// 通常,这将导致运行时异常,验证返回类型与 stubbed 方法签名。
invocationContainer.resetInvocationForPotentialStubbing(invocationMatcher);
return ret;
}
}
这个方法是 Mockito 框架中的一个重要方法,用于处理 mock 对象的方法调用。具体作用是:
-
如果存在可用于 stubbing 的答案,则使用答案来 stub 方法调用。
-
获取当前的 verificationMode,并检查是否在正确的 mock 上进行了 verify()。
-
准备 invocationMatcher 用于 stubbing,并报告 ongoingStubbing。
-
查找当前 invocation 的答案。
-
如果存在 stubbing,则将调用参数捕获到 stubbing 中,使用 stubbing 的答案来处理方法调用,并返回结果。
-
如果不存在 stubbing,则使用 defaultAnswer 来处理方法调用,并返回结果。
-
Mockito 使用 resetInvocationForPotentialStubbing 方法在部分模拟/间谍的情况下重新设置 invocationForPotentialStubbing 的调用,以避免 stubbing 错误的方法。
通过这个方法,Mockito 可以处理 mock 对象的方法调用,并使用 stubbing 或 defaultAnswer 来模拟真实的对象行为,在测试中验证方法调用是否正确。
org.mockito.internal.progress.ThreadSafeMockingProgress
此类有一个 属性用来存放 不同进程处理的方法
private static final ThreadLocal<MockingProgress> MOCKING_PROGRESS_PROVIDER =
new ThreadLocal<MockingProgress>() {
@Override
protected MockingProgress initialValue() {
return new MockingProgressImpl();
}
};
获取 当前进程处理的方法行为全局变量
public static final MockingProgress mockingProgress() {
return MOCKING_PROGRESS_PROVIDER.get();
}
when
获取该进程的全局变量,
获取到方法存根之后返回
public <T> OngoingStubbing<T> when(T methodCall) {
// 获取当前的 mockingProgress
MockingProgress mockingProgress = mockingProgress();
// 标记开始一个 stubbing 过程
mockingProgress.stubbingStarted();
// 获取当前正在进行的 stubbing
@SuppressWarnings("unchecked")
OngoingStubbing<T> stubbing = (OngoingStubbing<T>) mockingProgress.pullOngoingStubbing();
// 如果没有正在进行的 stubbing,则重置 mockingProgress 并抛出异常
if (stubbing == null) {
mockingProgress.reset();
throw missingMethodInvocation();
}
// 返回当前正在进行的 stubbing
return stubbing;
}
这个方法是 Mockito 框架中的一个重要方法,用于设置一个 mock 对象的行为。其具体作用是:
-
获取当前的 mockingProgress 对象,该对象用于管理当前的 mock 对象和 stubbing 过程。
-
标记开始一个 stubbing 过程,以便 Mockito 可以记录并验证对该 mock 对象的后续调用。
-
获取当前正在进行的 stubbing,该 stubbing 保存了对该 mock 对象进行的最近的 stubbing。
-
如果没有正在进行的 stubbing,则重置 mockingProgress 并抛出一个 MissingMethodInvocationException 异常。
-
返回当前正在进行的 stubbing 对象。
通过这个方法,Mockito 可以为 mock 对象设置一些预期的行为,以便在测试中模拟真实的对象行为。
thenReturn
private final InvocationContainerImpl invocationContainer;
@Override
public OngoingStubbing<T> thenAnswer(Answer<?> answer) {
// 检查是否存在一个可用于 stubbing 的调用
if (!invocationContainer.hasInvocationForPotentialStubbing()) {
throw incorrectUseOfApi();
}
// 添加一个答案到 invocationContainer 中
invocationContainer.addAnswer(answer, strictness);
// 返回一个 ConsecutiveStubbing 对象
return new ConsecutiveStubbing<T>(invocationContainer);
}
这个方法是 Mockito 框架中用于设置 mock 对象行为的方法之一,具体作用是:
-
检查是否存在一个可用于 stubbing 的调用。如果没有,则抛出一个异常。
-
将答案添加到 invocationContainer 中,以便在后续调用中使用该答案。
-
返回一个 ConsecutiveStubbing 对象,该对象可以用于指定多个 stubbing 行为,以便按顺序返回多个值。
通过这个方法,Mockito 可以为 mock 对象设置一个答案,以便在测试中模拟真实的对象行为。在实际的测试过程中,当 mock 对象的方法被调用时,Mockito 将按照预期的行为返回预定义的答案,从而模拟真实对象的行为,以便进行单元测试和集成测试。