Mockito 内部结构 [英] Mockito Internals

查看:20
本文介绍了Mockito 内部结构的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试了解 Mockito 的内部功能如何.到目前为止,代码对我来说很难理解,我正在寻找关于 Mockito 基本工作原理的高级调查.

I'm trying to understand how Mockito's internals function. So far the code has been difficult for me to understand, and I'm looking for a high-level survey of the fundamental workings of Mockito.

Mockito @ GrepCode

我编写了一些示例代码来展示我目前的理解:

I've written some sample code to demonstrate my current understanding:

class C {
    String s;
    public void getS() { return s; }
    // ...
}

C cm = mock( C.class);
when( cm.method() ).thenReturn( "string value");

据我所知,模拟"方法只看到 cm.getS() 的返回值.它如何知道方法的名称是什么(以便存根)?还有,它怎么知道传递给方法的参数?

As far as I can tell, the 'mock' method is only seeing the return value for cm.getS(). How can it know what the name of the method is (in order to stub it)? Also, how can it know the arguments passed to the method?

mockito API 方法调用内部对象的方法:

The mockito API method calls an internal object's method:

// org.mockito.Mockito
public static <T> OngoingStubbing<T> when(T methodCall) {
    return MOCKITO_CORE.when(methodCall);
}

我已经将方法调用跟踪到了几个不同的抽象以及类和对象中,但是代码如此解耦以至于很难理解这种方式.

I've followed the method invocations into several different abstractions and classes and objects, but the code is so decoupled that it is difficult to understand this way.

//  org.mockito.internal.MockitoCore
public <T> OngoingStubbing<T> when(T methodCall) {
    mockingProgress.stubbingStarted();
    return (OngoingStubbing) stub();
}

因此,如果有人了解内部情况或有讨论/博客文章的链接,请分享:)

So if anyone understands the internals or has a link to a discussion/blog post, please share :)

推荐答案

(对不起,这有点长.TL;DR:Mockito 记录了幕后的方法调用.)

(Sorry this got long. TL;DR: Mockito records the method call behind the scenes.)

C cm = mock(C.class);

此时,您可能会认为 cmC 的一个实例...您就错了.相反,cmproxy object Mockito 编写实现 C (因此可以分配给 C 类型的字段/变量)但记录您要求的所有内容并行为方式与您存根的方式相同.

At this point, you may think that cm is an instance of C...and you would be wrong. Instead, cm is an instance of a proxy object Mockito writes that implements C (and thus can be assigned to fields/variables of type C) but records everything you ask for and behaves the way you stub it to.

让我们手动编写一个模拟类...并再给它一个方法,比如说 int add(int a, int b),它添加了 a 和 <实际类中的code>b.

Let's write a mock class manually...and give it one more method, let's say int add(int a, int b), which adds a and b in the actual class.

class MockC extends C {
  int returnValue;

  @Override int add(int a, int b) {
    return returnValue;
  }
}

那里!现在,每当您调用 add 时,它不会将两个数字相加,而是只返回单个返回值.说得通.但是,如果您想稍后验证调用怎么办?

There! Now whenever you call add, it won't add the two numbers, but instead just return the single return value. Makes sense. But what if you want to verify the calls later?

class MockC extends C {
  List<Object[]> parameterValues = new ArrayList<>();
  int returnValue;

  @Override int add(int a, int b) {
    parameterValues.add(new Object[] { a, b });
    return returnValue;
  }
}

所以现在您可以检查 parameterValues 列表并确保它被按预期调用.

So now you can check the parameterValues list and make sure it was called as expected.

事情是这样的:Mockito 生成一个 proxy 使用 CGLIB 像 MockC 一样自动运行,保留所有交互和返回值在一个大的静态列表中.该列表称为 RegisteredInvocations,而不是每个方法调用的 Object[]每个模拟都是一个 Invocation,但思路是一样的.

Here's the thing: Mockito generates a proxy using CGLIB that acts like MockC automatically, keeping all of the interactions and return values in one big static list. The list is called RegisteredInvocations, and instead of an Object[] every method call for every mock is an Invocation, but the idea is the same.

要进一步了解 RegisteredInvocations 以及它公开的 removeLast 方法为何如此重要,请阅读 InvocationContainer.因为 Mockito 记录了每一次调用,它自然会记录 when 中包含的交互.一旦 Mockito 看到 when,它删除最后记录的交互 (InvocationContainerImpl.java 第 45 行)并将其用作存根的模板--从调用对象本身读取参数值.

To understand a little more about RegisteredInvocations and why the removeLast method it exposes is so important, read the code in InvocationContainer. Because Mockito records every call, it will naturally record the interaction contained within when. As soon as Mockito sees when, it removes the last recorded interaction (InvocationContainerImpl.java line 45) and uses it as the template for your stubbing--reading the argument values from the Invocation object itself.

这可以解决大部分问题,除了 eqany 之类的参数匹配器:原来这些只是 保留在一个花哨的堆栈上 称为 ArgumentMatcherStorage.when 调用 检查堆栈上有多少匹配器:对于 add 示例,零匹配器告诉 Mockito 推断与每个记录的相等参数,两个匹配器告诉 Mockito 将它们从堆栈中弹出并使用它们.只有一个匹配器意味着 Mockito 无法判断您要匹配哪个整数并抛出经常令人困惑的 InvalidUseOfMatchersException,这就是为什么在使用匹配器时需要匹配每个如果你匹配任何参数.

That takes care of most of it, except for argument matchers like eq and any: Turns out that those are just kept on a fancy stack called ArgumentMatcherStorage. The when call checks how many matchers are on the stack: For the add example, zero matchers tells Mockito to infer equality with every recorded argument, and two matchers tells Mockito to pop those off the stack and use those. Just one matcher means Mockito can't tell which integer you're trying to match and throws the often-confusing InvalidUseOfMatchersException, which is why when using matchers you need to match every argument if you match any at all.

希望有帮助!

这个答案描述了如何when 方法的工作原理更详细.

This answer describes how the when method works in more technical detail.

这篇关于Mockito 内部结构的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆