springboot之当我们 mock 一个方法, Mockito 帮我们做了什么

当我们 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 方法处理方法调用并返回结果。具体作用是:

  1. 为了避免 JVM JIT 在方法执行期间对 mock 实例进行垃圾回收,我们使用一个线程本地变量将其逃逸出去。这样可以确保在 handler.handle(…) 方法完成前,mock 实例不会被回收。

  2. 调用 handler 的 handle 方法,处理 mock 对象的方法调用,并返回结果。

  3. 移除线程本地变量中的 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 对象的方法调用。具体作用是:

  1. 如果存在可用于 stubbing 的答案,则使用答案来 stub 方法调用。

  2. 获取当前的 verificationMode,并检查是否在正确的 mock 上进行了 verify()。

  3. 准备 invocationMatcher 用于 stubbing,并报告 ongoingStubbing。

  4. 查找当前 invocation 的答案。

  5. 如果存在 stubbing,则将调用参数捕获到 stubbing 中,使用 stubbing 的答案来处理方法调用,并返回结果。

  6. 如果不存在 stubbing,则使用 defaultAnswer 来处理方法调用,并返回结果。

  7. 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 对象的行为。其具体作用是:

  1. 获取当前的 mockingProgress 对象,该对象用于管理当前的 mock 对象和 stubbing 过程。

  2. 标记开始一个 stubbing 过程,以便 Mockito 可以记录并验证对该 mock 对象的后续调用。

  3. 获取当前正在进行的 stubbing,该 stubbing 保存了对该 mock 对象进行的最近的 stubbing。

  4. 如果没有正在进行的 stubbing,则重置 mockingProgress 并抛出一个 MissingMethodInvocationException 异常。

  5. 返回当前正在进行的 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 对象行为的方法之一,具体作用是:

  1. 检查是否存在一个可用于 stubbing 的调用。如果没有,则抛出一个异常。

  2. 将答案添加到 invocationContainer 中,以便在后续调用中使用该答案。

  3. 返回一个 ConsecutiveStubbing 对象,该对象可以用于指定多个 stubbing 行为,以便按顺序返回多个值。

通过这个方法,Mockito 可以为 mock 对象设置一个答案,以便在测试中模拟真实的对象行为。在实际的测试过程中,当 mock 对象的方法被调用时,Mockito 将按照预期的行为返回预定义的答案,从而模拟真实对象的行为,以便进行单元测试和集成测试。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值