Mockito:使用类型兼容的参数验证重载方法

时间:2015-12-14 11:19:49

标签: java eclipse mockito overload-resolution ecj

考虑使用包含以下方法签名的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) 进行验证?

2 个答案:

答案 0 :(得分:2)

这不是一个模拟问题。

在进一步调查期间,I realized that the actual method is chosen at compile-time (JLS §15.12.2)。所以基本上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语言规范非常严格。

答案 1 :(得分:2)

我同意其他答案中的大多数,只有一部分尚未得到答复:为什么编译器存在差异?

仔细看看这不是ecj和javac之间的区别,而是在不同版本的JLS之间:

  • 在符合1.7或更低的情况下进行编译,两个编译器都选择第一个(单个参数)方法。
  • 在合规性1.8中编译,两个编译器都选择第二个(varargs)方法

让我们看看生成的字节码:

     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()。这意味着在没有varargs魔法的情况下也可以调用doThis(Object...),但是通过传递Object[]类型的单个参数。

现在两种方法都适用于同一类别("适用于固定的arity调用")并且在适用的方法中搜索最具体的方法选择第二种方法。

相比之下,Java 7只允许调用第二种方法作为变量调用,但如果找到固定的arity匹配则甚至不会尝试。

以上还说明了如何使程序对JLS中的更改具有鲁棒性:

verify(mock, times(1)).doThis(Matchers.<Object>anyObject());

这将告诉所有版本的所有编译器选择第一种方法,因为现在它总是会将doThis()的参数看作Object - 如果你真的无法避免这种不健康超载,即:)