如何验证仅通过反射获得的java.lang.reflect.Method是否在对象上被调用

huangapple go评论144阅读模式
英文:

How to verify a java.lang.reflect.Method known only from reflection was called on an object

问题

通过反射获得的 java.lang.reflect.Method 对象,我该如何测试该方法是否在给定的对象上被调用?

以下是示例代码:

MyClass obj = mock(MyClass.class);
injectInTestedObject(obj, testedObj);

String myMethodIWantToCheckName = retrieveMethodNameBySomeMeans();
Method myMethodIWantToCheck =
  Arrays.asList(MyClass.class.getMethods())
    .stream()
    .filter(method -> method.getName().equals(myMethodIWantToCheckName))
    .findFirst()
    .get();

现在我想检查 myMethodIWantToCheck 是否在 obj 上被调用。

基本上,这与 Mockito 的 verify 功能相同,但在编译时不知道方法名。

是否有任何方法?是否已经有库实现了这个功能?

英文:

Given a java.lang.reflect.Method object obtained through reflection, how can I test that this Method was called on a given object ?

Here is an example code :

MyClass obj = mock(MyClass.class);
injectInTestedObject(obj, testedObj);

String myMethodIWantToCheckName = retrieveMethodNameBySomeMeans();
Method myMethodIWantToCheck =
  Arrays.asList(MyClass.class.getMethods())
    .stream()
    .filter(method -> method.getName().equals(myMethodIWantToCheckName))
    .findFirst()
    .get();

Now I'd like to check if myMethodIWantToCheck was called on obj.

Basically it's the same functionality as verify from Mockito but without knowing the method at compile time.

Is there any way ? Any library already implementing this ?

答案1

得分: 2

不,显然在仅使用Java SE提供的库的情况下,没有简短和简单的方法来实现这一点。而且对于这一点,我感到非常高兴!

“朴素”的方法

假设您想要实现的内容实际上是可能的,并且在java.lang.reflect API中支持的操作。为了提供这样的功能,需要哪种信息和机制?如果某人能够检索某个方法是否已经在任何时间(没有任何“开始分隔符”)至少被调用过一次(在某个对象上),则需要Java虚拟机跟踪每次调用的情况;这实际上迫使它持久化所有已创建的堆栈帧!引入这样的开始分隔符可能会减少运行时开销,但仍然需要一种极其复杂的堆栈帧审计形式,内置于每个JVM中。

Mockito的实现方式

但是嘿,Mockito能够提供这样的功能,对吧?确实,但我认为您低估了它们的实现复杂性。

首先,您的方法和Mockito的方法之间存在一个非常重要的概念上的区别。这在查看Mockito网站上的第一个示例时就已经变得明显:

import static org.mockito.Mockito.*;

// 创建模拟对象
List mockedList = mock(List.class);

// 使用模拟对象 - 它不会抛出任何“意外交互”异常
mockedList.add("one");
mockedList.clear();

// 选择性、显式、高可读性的验证
verify(mockedList).add("one");
verify(mockedList).clear();

该框架的用户始终需要首先模拟感兴趣的类型;可以像这里一样明确地创建模拟对象,也可以通过使用注解更加隐式地实现。由于我对Mockito框架不太熟悉,可能还有更加优雅的方法;但归根结底都是相同的概念。

但是“模拟一个类型”到底意味着什么,它应该有什么作用?实际上,Mockito必须以某种方式拦截对模拟方法返回的对象的任何调用(或者其他机制,分别是如此)。幸运的是(牢记本文开头的内容),Mockito 无法直接这样做,因此开发人员必须提出其他方法:在运行时动态创建新类型,这些类型不仅将方法调用委托给实际实现,还会跟踪在该特定类型上调用的方法。

查看该框架的源代码,似乎“真正的魔法”发生在org.mockito.internal.creation.bytebuddy 包中。在SubclassBytecodeGenerator 中,您可以找到Mockito的类型创建逻辑。正如包名已经暗示的,Mockito 使用Byte Buddy 框架 来进行字节码生成和加载过程。

库支持

既然您还询问了一个可以用来实现相同行为的库:据我所知,目前没有什么_真正著名的库_可供选择。

老实说:对此我并不感到惊讶。我的意思是,人们为什么要使用这样的库呢?检查方法是否被调用是应该在测试中进行的事情;其他用途相当有限。

所以,是的。我想您的选择相当有限。

英文:

No, there's apparently no short and easy way to do so using only the libraries provided by the Java SE. And I couldn't be happier about this!

The naive approach

Let's imagine that what you're trying to achieve was actually possible and a supported operation in the java.lang.reflect-API. What kind of information and mechanisms would be required to provide such a functionality? In case one should be able to retrieve whether a certain method has already been invoked (on a certain object) at least once at any time (with no kind of "start delimiter"), the Java Virtual Machine would be required to keep track of any invocation ever made; effectively forcing it to persist all stack frames ever created! Introducing such a start delimiter may possibly reduce the runtime overhead, but would still require an extremely complex form of stack frame auditing, built into every JVM.

How Mockito is doing it

But hey, Mockito is able to provide such a functionality, right? Indeed, but I think you're underestimating the complexity of the way they're doing that.

First, there's a very important, conceptual difference between your and Mockito's approach. It already becomes apparent when taking a look at the very first example on the Mockito website:

import static org.mockito.Mockito.*;

// mock creation
List mockedList = mock(List.class);

// using mock object - it does not throw any "unexpected interaction" exception
mockedList.add("one");
mockedList.clear();

// selective, explicit, highly readable verification
verify(mockedList).add("one");
verify(mockedList).clear();

Users of the framework always have to mock the types of interest first; either explicitly like here or in a more implicit manner by using annotations. As I'm not very familiar with the Mockito framework, there may be even more elegant ways to do so; but it all boils down to the very same concept.

But what does "mocking a type" even mean and what should it be good for? Actually, Mockito has to somehow intercept any call that is being made to the object returned by the mocking method (or the other mechanisms, respectively). Fortunately (keeping the beginning of this post in mind), Mockito isn't able to do so directly, so the developers had to come up with something else: Creating new types dynamically at runtime that are not only delegating the method calls to the actual implementation, but that also keep track of the methods that are being invoked on that particular type.

Looking at the framework's source code, it seems like that the real magic is happening in the org.mockito.internal.creation.bytebuddy package. In SubclassBytecodeGenerator, you can find Mockito's type creation logic. As the package name already implies, Mockito uses the Byte Buddy framework for the bytecode generation and loading process.

Library support

As you've also asked for a library which you may use to achieve the same behavior: As far as I can tell, there's no really well-known library out there.

To be honest: I'm not surprised that that's the case. I mean, what should one use such a library for? Checking whether a method has been invoked is something one should do in tests; other use cases are quite limited.

So, yeah. Your options are quite limited, I suppose.

huangapple
  • 本文由 发表于 2020年4月3日 23:16:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/61014998.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定