Mockito:使用类型兼容的参数验证重载方法
Mockito: Verifying overloaded methods with type-compatible arguments
考虑到您想要 mock 一个使用 Mockito
的接口,其中包含以下方法签名:
public void doThis(Object o);
public void doThis(Object... o)
我需要验证doThis(Object o)
(而不是其他方法)已被调用一次。
首先我认为下面一行可以解决问题:
verify(mock, times(1)).doThis(anyObject());
然而,由于这似乎适用于 Windows,它不适用于 Linux,因为在这种环境中,需要调用另一个 doThis
方法。
这是因为 anyObject()
参数似乎与两个方法签名都匹配 并且以或多或少不可预测的方式选择了一个。
我如何强制 Mockito 始终选择 doThis(Object o)
进行验证?
这不是 mockito 问题。
在进一步调查中,。所以基本上 class 文件在 windows 和 linux 之间是不同的,这导致了不同的 mockito 行为。
界面不受欢迎(参见Effective Java, 2nd Edition, Item 42
)。
我将其更改为匹配以下内容:
public void doThis(Object o);
public void doThis(Object firstObj, Object... others)
通过此更改,将始终选择预期的(第一个)方法。
还有一点:
为什么 windows 上的 java 编译器(eclipse 编译器)产生的输出与 Linux 上的 Oracle JDK javac 产生的输出不同?
这可能是 ECJ 中的错误,因为我认为 java 语言规范在这里非常严格。
我同意其他答案中的大部分,只有一部分尚未回答:为什么编译器不同?
仔细一看,这不是ecj和javac之间的区别,而是不同版本的JLS之间的区别:
- 编译符合 1.7 或更低,两个编译器 select 第一个(单参数)方法。
- 编译符合 1.8,两个编译器 select 第二种(可变参数)方法
让我们看看生成的字节码:
7: invokestatic #8 // Method anyObject:()Ljava/lang/Object;
10: checkcast #9 // class "[Ljava/lang/Object;"
13: invokevirtual #10 // Method doThis:([Ljava/lang/Object;)V
(两个编译器也同意这些字节)
这就是说:Java 8 中的推理变得更加强大,现在能够将 anyObject()
的类型参数推断为 Object[]
。这可以通过 checkcast
指令看出,它被翻译回源代码:(Object[])anyObject()
。这意味着 doThis(Object...)
也可以在没有可变参数魔法的情况下被调用,但是通过传递类型为 Object[]
.
的单个参数
现在这两种方法都适用于同一类别("applicable by fixed arity invocation")并在适用的方法中搜索最具体的select第二种方法。
相比之下,Java 7 将允许仅将第二种方法作为可变元数调用来调用,但如果找到固定元数匹配,则甚至不会尝试此方法。
以上也说明了如何使程序对JLS的变化具有健壮性:
verify(mock, times(1)).doThis(Matchers.<Object>anyObject());
这将告诉所有版本的所有编译器 select 第一种方法,因为现在它总是将 doThis()
的参数视为 Object
-- 如果你真的可以'不要避免这种不健康的超载,即 :)
考虑到您想要 mock 一个使用 Mockito
的接口,其中包含以下方法签名:
public void doThis(Object o);
public void doThis(Object... o)
我需要验证doThis(Object o)
(而不是其他方法)已被调用一次。
首先我认为下面一行可以解决问题:
verify(mock, times(1)).doThis(anyObject());
然而,由于这似乎适用于 Windows,它不适用于 Linux,因为在这种环境中,需要调用另一个 doThis
方法。
这是因为 anyObject()
参数似乎与两个方法签名都匹配 并且以或多或少不可预测的方式选择了一个。
我如何强制 Mockito 始终选择 doThis(Object o)
进行验证?
这不是 mockito 问题。
在进一步调查中,
界面不受欢迎(参见Effective Java, 2nd Edition, Item 42
)。
我将其更改为匹配以下内容:
public void doThis(Object o);
public void doThis(Object firstObj, Object... others)
通过此更改,将始终选择预期的(第一个)方法。
还有一点:
为什么 windows 上的 java 编译器(eclipse 编译器)产生的输出与 Linux 上的 Oracle JDK javac 产生的输出不同?
这可能是 ECJ 中的错误,因为我认为 java 语言规范在这里非常严格。
我同意其他答案中的大部分,只有一部分尚未回答:为什么编译器不同?
仔细一看,这不是ecj和javac之间的区别,而是不同版本的JLS之间的区别:
- 编译符合 1.7 或更低,两个编译器 select 第一个(单参数)方法。
- 编译符合 1.8,两个编译器 select 第二种(可变参数)方法
让我们看看生成的字节码:
7: invokestatic #8 // Method anyObject:()Ljava/lang/Object;
10: checkcast #9 // class "[Ljava/lang/Object;"
13: invokevirtual #10 // Method doThis:([Ljava/lang/Object;)V
(两个编译器也同意这些字节)
这就是说:Java 8 中的推理变得更加强大,现在能够将 anyObject()
的类型参数推断为 Object[]
。这可以通过 checkcast
指令看出,它被翻译回源代码:(Object[])anyObject()
。这意味着 doThis(Object...)
也可以在没有可变参数魔法的情况下被调用,但是通过传递类型为 Object[]
.
现在这两种方法都适用于同一类别("applicable by fixed arity invocation")并在适用的方法中搜索最具体的select第二种方法。
相比之下,Java 7 将允许仅将第二种方法作为可变元数调用来调用,但如果找到固定元数匹配,则甚至不会尝试此方法。
以上也说明了如何使程序对JLS的变化具有健壮性:
verify(mock, times(1)).doThis(Matchers.<Object>anyObject());
这将告诉所有版本的所有编译器 select 第一种方法,因为现在它总是将 doThis()
的参数视为 Object
-- 如果你真的可以'不要避免这种不健康的超载,即 :)