3

我有一个Sut使用延迟初始化实现的类java.util.function.Supplier。事实上它比下面的代码更复杂,但这是 Mockito 无法测试的最简单的形式。下面的测试会引发错误Wanted but not invoked ... However, there were other interactions with this mock。为什么 Mockito 不计算 的调用create?代码流实际进入create();我用调试器检查过。

import java.util.function.Supplier;

import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

public class TestTimes {

    @Test
    public void testCreateOnlyOnce() {
        Sut sut = spy(new Sut());
        sut.getData();
        sut.getData();
        sut.getData();
        verify(sut, times(1)).create();
    }

    private static class Sut {
        Supplier<Object> data = this::create;

        void getData() {
            data.get();
        }

        Object create() {
            return new Object();
        }
    }
}
4

2 回答 2

3

首先,感谢您写得很好的问题。

我自己测试了你的代码,看到了你提到的错误。虽然,我在调试时稍微更改了您的代码......看看:

    @Test
    public void testCreateOnlyOnce() {
        Sut sut = spy(new Sut());
        sut.getData();
        sut.getData();
        sut.getData();
        verify(sut, times(1)).create();
    }

    private static class Sut {

        private Supplier<Object> data;

        // Added de data object initialization on the constructor to help debugging.
        public Sut() {
            this.data = this::create;
        }

        void getData() {
            data.get();
        }

        Object create() {
            return new Object();
        }
    }

我在调试时发现:

  1. 在子句中Sut正确调用了类构造函数,但没有在那里调用该方法。spy(new Sut())create()
  2. 每次sut.getData()被调用,create()方法也被调用。是什么让我得出结论,最后是:

在构造函数上,this::create所做的只是告诉 java,无论何时需要Object从供应商那里检索 ,Object都将从create()方法中检索。而且,create()供应商调用的方法来自一个不同于 Mockito 正在监视的类实例。

这就解释了为什么您无法通过验证对其进行跟踪。

编辑:根据我的研究,这实际上是供应商的期望行为。它只是创建一个接口,该接口具有get()调用您在方法引用上声明的任何 noArgs 方法的方法。在“使用方法参考实例化供应商”中查看此内容。

于 2019-06-27T00:16:43.730 回答
3

我想为 Gabriel Pimentas 出色的答案添加一点内容。这样做的原因是 mockito 创建了 spy 的浅表副本,new Sut()并且您Supplier指的是原始Sut实例中编译的 lambda 方法,而不是 spy 实例。

另请参阅此问题mockito 文档

调试代码时,您可以看到它是如何工作的:

Sut sut = spy(new Sut());现在是Sut作为实例的模拟/间谍子类TestTimes$Sut$MockitoMock$1381634547@5b202a3a。现在,原始的所有字段new Sut()都是浅拷贝的,包括Supplier<Object> data. 查看 spy 中的这个字段,我们可以看到它是 的一个实例TestTimes$Sut$$Lambda$1/510109769@1ecee32c,即指向原始 中的一个 lambda Sut。当我们在 create 方法中设置断点时,我们可以进一步观察到this指向TestTimes$Sut@232a7d73,即原始Sut实例而不是被窥探的实例。

编辑:即使此 MCVE 可能与您的实际代码不同,但仍会想到以下解决方案:

  • 将供应商注入您的Sut(在构造期间或作为getData.
  • 在您的方法中懒惰地创建供应商getData(使其指向模拟实例)
  • 不要使用供应商,create如果供应商不是从外部传递的,则直接调用
于 2019-06-27T07:55:29.393 回答