【问题标题】:PowerMockito / Mockito argument matcher call location issuePowerMockito / Mockito 参数匹配器调用位置问题
【发布时间】:2021-07-13 16:49:26
【问题描述】:

简而言之,我有一组生成的源代码,我需要能够基于外部的非 Java 配置动态模拟它们——它们没有遵循一致的模式/实现除静态之外的任何接口,这意味着我只能知道如何在运行时模拟方法并且需要使用 PowerMockito 来做到这一点。

说我有这门课:

public class SomeClass {
  public static void doSomething(Integer i) {
    throw new RuntimeException();
  }
}

我只是想模拟 doSomething / 让它不抛出异常。为了简单/没有我在用例中提到的任何复杂性,我可以这样做:

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
@PrepareForTest(SomeClass.class)
public class TestSomeClass {

  @Test
  public void testDoSomethingSimple() throws Exception {

    PowerMockito.spy(SomeClass.class);
    PowerMockito.doNothing().when(SomeClass.class, "doSomething", any(Integer.class));

    SomeClass.doSomething(5);
  }
}

效果很好。

然而,当我们退后一步并尝试解决我的需求并将复杂性转移到这样的事情时,情况就会发生变化:

  @Test
  public void testDoSomething() throws Exception {
    // Below showing how everything could be externally-driven
    testDoSomething("SomeClass", "doSomething", "java.lang.Integer");
    SomeClass.doSomething(5);
  }

  public void testDoSomething(
      final String canonicalClassName, final String methodName, final String... canonicalParameterClassNames)
      throws Exception {

    final Class<?> clazz = Class.forName(canonicalClassName);

    PowerMockito.spy(clazz);

    final Object[] argumentMatchers = new Object[canonicalParameterClassNames.length];
    for (int i = 0; i < canonicalParameterClassNames.length; i++) {
      argumentMatchers[i] = any(Class.forName(canonicalParameterClassNames[i]));
    }

    PowerMockito.doNothing().when(clazz, methodName, argumentMatchers);
  }

这会导致这个问题:

经过多次摸索,设法更简洁地复制了这个错误:

  @Test
  public void testDoSomethingIssueIsolated() throws Exception {

    PowerMockito.spy(SomeClass.class);
    Object matcher = any(Integer.class);
    PowerMockito.doNothing().when(SomeClass.class, "doSomething", matcher);

    SomeClass.doSomething(5);
  }

似乎表明导致此问题的原因是 创建参数匹配器的调用在哪里,这很奇怪。

【问题讨论】:

  • 您能否将您的 pom.xml 与您正在使用的库版本共享?

标签: java junit mockito variadic-functions powermock


【解决方案1】:

知道了 - 这不是 PowerMockito 的东西。这是一个标准的 Mockito 东西,实际上是设计使然 - 说明点是错误中的一个词 - 你不能在验证或存根之外使用参数匹配器。虽然我将它们用于存根,但外部暗示更多。

这让我找到了this answer to another question on how matchers work,并附上了特别重要的评论:

- 调用顺序不仅重要,它也是使这一切正常工作的原因。 将匹配器提取到变量通常不起作用,因为它 通常会更改调用顺序。将匹配器提取到方法中, 但是,效果很好。

int between10And20 = and(gt(10), lt(20));
/* BAD */ when(foo.quux(anyInt(), between10And20)).thenReturn(true);
// Mockito sees the stack as the opposite: and(gt(10), lt(20)), anyInt().

public static int anyIntBetween10And20() { return and(gt(10), lt(20)); }
/* OK */  when(foo.quux(anyInt(), anyIntBetween10And20())).thenReturn(true);
// The helper method calls the matcher methods in the right order.

基本上必须小心堆栈,这导致我这样做,它可以工作并满足我能够模拟在运行时确定的可变数量参数的要求(testDoSomething 中的字符串都可以从文本文件中提取并且可以通过反射管理方法调用):

  @Test
  public void testDoSomething() throws Exception {
    // Below showing how everything could be externally-driven
    mockAnyMethod("SomeClass", "doSomething", "java.lang.Integer");
    SomeClass.doSomething(5);
  }

  public void mockAnyMethod(
      final String canonicalClassName,
      final String methodName,
      final String... canonicalParameterClassNames)
      throws Exception {

    final Class<?> clazz = Class.forName(canonicalClassName);

    PowerMockito.spy(clazz);

    PowerMockito.doNothing()
        .when(clazz, methodName, getArgumentMatchers(canonicalParameterClassNames));
  }

  public Object[] getArgumentMatchers(final String... canonicalParameterClassNames)
      throws ClassNotFoundException {

    final Object[] argumentMatchers = new Object[canonicalParameterClassNames.length];
    for (int i = 0; i < canonicalParameterClassNames.length; i++) {
      argumentMatchers[i] = any(Class.forName(canonicalParameterClassNames[i]));
    }
    return argumentMatchers;
  }

【讨论】:

  • 很好的“破解”,可以留在验证或存根中。
  • 我立即使用了您的解决方案private &lt;T&gt; T matchEqOrIsNull(T argument) { return argument == null ? isNull() : eq(argument); },因为nullable 仅适用于类型
【解决方案2】:

如果你仔细阅读失败轨迹,你就会找到这个问题的答案

Misplaced or misused argument matcher detected here:

-> at mockito.TestSomeClass.testDoSomething(TestSomeClass.java:xx)

You cannot use argument matchers outside of verification or stubbing.
Examples of correct usage of argument matchers:
    when(mock.get(anyInt())).thenReturn(null);
    doThrow(new RuntimeException()).when(mock).someVoidMethod(anyObject());
    verify(mock).someMethod(contains("foo"))

您尝试在 for 循环中使用 any(...),这超出了验证或存根(此处为:PowerMockito.doNothing().when(...))。

for (int i = 0; i < canonicalParameterClassNames.length; i++) {
  argumentMatchers[i] = any(Class.forName(canonicalParameterClassNames[i]));
}

PowerMockito.doNothing().when(clazz, methodName, argumentMatchers);

因此,您的解决方案将不起作用。

我尝试了这个替代方法

    for (int i = 0; i < canonicalParameterClass.length; i++) {
        PowerMockito.doNothing().when(clazz, methodName, any(Class.forName(canonicalParameterClass[i])));
    }

这对我有用。

您可以通过使用Class 而不是String 作为类名来简化您的方法。

    @Test
    public void testDoSomething() throws Exception {
        // Below showing how everything could be externally-driven
        testDoSomething(SomeClass.class, "doSomething", Integer.class);
        SomeClass.doSomething(5);
    }

    public void testDoSomething(final Class classToTest, final String methodName, final Class... parameterClasses)
            throws Exception {

        PowerMockito.spy(classToTest);

        for (int i = 0; i < parameterClasses.length; i++) {
            PowerMockito.doNothing().when(classToTest, methodName, any(parameterClasses[i]));
        }
    }
}

【讨论】:

  • 这行不通,原因与您的 cmets 之一有关 - 将类表示为字符串的原因是因为其想法是它们将由外部配置驱动(即文本文件) / 直到运行时才知道。具体来说,这只适用于模拟使用单个输入参数多次重载的单个方法 - 它不适用于每个方法的可变数量参数,即 doSomething(Integer i)、doSomething(Integer i, String s)。话虽如此,促使我根据下面的回答进一步挖掘。
猜你喜欢
  • 1970-01-01
  • 2014-05-21
  • 2022-01-23
  • 1970-01-01
  • 2013-05-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多