使用 Mockito j.u.f.Supplier 测试延迟初始化

Testing lazy initialization by j.u.f.Supplier with Mockito

我有一个 class 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();
        }
    }
}

首先,感谢您提出的问题。

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

    @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. spy(new Sut()) 子句中正确调用了 Sut class 构造函数,但未在那里调用 create() 方法。
  2. 每次调用sut.getData()时,也会调用create()方法。是什么让我得出结论,最终

On the constructor, all that this::create did was telling java that, whenever it needs to retrieve the Object from the supplier, that Object will be retrieved from the create() method. And, the create() method being called by the supplier is from a class instance different from what Mockito is spying.

这就解释了为什么你不能用验证来跟踪它。

编辑:根据我的研究,这实际上是供应商期望的行为。它只是创建一个具有 get() 方法的接口,该方法调用您在方法引用上声明的任何 noArgs 方法。看看 "Instantiate Supplier Using Method Reference" 上的 this

我想补充一点 Gabriel Pimentas 的出色回答。这样做的原因是 mockito 创建了间谍 new Sut() 的浅表副本,而您的 Supplier 指的是原始 Sut 实例中的已编译 lambda 方法,而不是间谍实例。

另见 and the mockito documentation

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

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

编辑:尽管此 MCVE 可能与您的实际代码不相似,但我会想到以下解决方案:

  • 将 Supplier 注入您的 Sut(在构造期间或作为 getData 的参数。
  • 在您的 getData 方法中延迟创建供应商(使其指向 mockito-instance)
  • 不使用 Supplier,如果 Supplier 不是从外部传递的,直接调用 create