为什么 Mockito 在验证另一个模拟对象时访问一个模拟对象会抛出异常?

huangapple 未分类评论71阅读模式
英文:

Why does Mockito throw up when accessing a mock while verifying another one?

问题

以下是翻译好的内容:

查看这个示例:

class Foo { }

class Bar {
    void takeIt(int i, String arg) { System.out.println(arg + i); }
}

public class Mcve {    
    @Test
    public void passes() {
        Foo foo = Mockito.mock(Foo.class);
        Bar bar = Mockito.mock(Bar.class);
        bar.takeIt(42, "surprise: " + foo);
        String substring = "surprise: " + foo;
        Mockito.verify(bar).takeIt(ArgumentMatchers.eq(42),
                ArgumentMatchers.contains(substring));
    }

    @Test
    public void fails() {
        Foo foo = Mockito.mock(Foo.class);
        Bar bar = Mockito.mock(Bar.class);
        bar.takeIt(42, "surprise: " + foo);
        Mockito.verify(bar).takeIt(ArgumentMatchers.eq(42),
                ArgumentMatchers.contains("surprise: " + foo));
    }    
}

这两个测试几乎完全相同,唯一的区别是用于 contains() 匹配器的字符串在 passes() 中提前计算,但在 fails() 中是内联的。

fails() 抛出异常:

org.mockito.exceptions.misusing.InvalidUseOfMatchersException: 
Invalid use of argument matchers!
0 matchers expected, 1 recorded:
-> at com.ibm.hwmca.z.svm.zhyp.managed.Mcve.fails(Mcve.java:43)

This exception may occur if matchers are combined with raw values:
    //incorrect:
    someMethod(anyObject(), "raw String");
When using matchers, all arguments have to be provided by matchers.
For example:
    //correct:
    someMethod(anyObject(), eq("String by matcher"));

For more info see javadoc for Matchers class.


    at java.lang.String.valueOf(String.java:2994)
    at java.lang.StringBuilder.append(StringBuilder.java:131)
    at Mcve.fails(Mcve.java:43)
...

(显然,错误信息是错误的,因为上面的代码对所有参数都使用了匹配器)

更有趣的是:仅在需要匹配超过一个参数时才会失败(如果从 gimme() 中删除 int 参数,并且只传递/匹配该字符串参数:pass)。

有人能解释一下这里到底发生了什么,并且是否有一种方法可以进行这样的匹配,比如 contains("surprise: " + foo),其中 foo 是由 Mockito 模拟的吗?


当然,这实际上是一个 MCVE(最小可复现示例)。我在我们的环境中花了3个小时,从失败的单元测试得到了上面的示例。

在实际环境中,Bar 类是一个模拟的日志记录工具。
而 Foo 对象表示由某个虚拟持久层创建的某种“数据实体”。我必须验证生产代码是否记录了特定的信息,其中一些信息是从虚拟数据对象中派生出来的。

英文:

See this example:

class Foo { }

class Bar {
    void takeIt(int i, String arg) { System.out.println(arg + i); }
}

public class Mcve {    
    @Test
    public void passes() {
        Foo foo = Mockito.mock(Foo.class);
        Bar bar = Mockito.mock(Bar.class);
        bar.takeIt(42, "surprise: " + foo);
        String substring = "surprise: " + foo;
        Mockito.verify(bar).takeIt(ArgumentMatchers.eq(42),
                ArgumentMatchers.contains(substring));
    }

    @Test
    public void fails() {
        Foo foo = Mockito.mock(Foo.class);
        Bar bar = Mockito.mock(Bar.class);
        bar.takeIt(42, "surprise: " + foo);
        Mockito.verify(bar).takeIt(ArgumentMatchers.eq(42),
                ArgumentMatchers.contains("surprise: " + foo));
    }    
}

The two tests are almost identical, the only difference: the string used for the contains() matcher is computed upfront in passes(), but inlined in fails().

fails() throws up:

org.mockito.exceptions.misusing.InvalidUseOfMatchersException: 
Invalid use of argument matchers!
0 matchers expected, 1 recorded:
-> at com.ibm.hwmca.z.svm.zhyp.managed.Mcve.fails(Mcve.java:43)

This exception may occur if matchers are combined with raw values:
    //incorrect:
    someMethod(anyObject(), "raw String");
When using matchers, all arguments have to be provided by matchers.
For example:
    //correct:
    someMethod(anyObject(), eq("String by matcher"));

For more info see javadoc for Matchers class.


	at java.lang.String.valueOf(String.java:2994)
	at java.lang.StringBuilder.append(StringBuilder.java:131)
	at Mcve.fails(Mcve.java:43)
...

(Obviously: the error message is plain wrong, as the above code is using a matcher for all parameters)

Even more interesting: it only fails with more than 1 argument to match on (if one removes the int parameter from gimme(), and just passes/matches that string argument: pass).

Can anyone explain exactly what is happening here, and is there a way to do such a matching like contains("surprise: " + foo), with foo being something Mockito-mocked?


Of course, this is really meant as MCVE. It took me 3 hours to get from the failing unit test in our environment to this example here.

In the real environment, the Bar class is a mocked logging facility.
And the Foo object represents some "data entity" that gets created by some fake persistence layer. I have to verify that the production code does log specific information, and some of that information is derived from the faked data objects.

huangapple
  • 本文由 发表于 2020年8月20日 22:24:02
  • 转载请务必保留本文链接:https://java.coder-hub.com/63507212.html
匿名

发表评论

匿名网友

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

确定