Java j.u.f.供应商使用Mockito测试延迟初始化

Java j.u.f.供应商使用Mockito测试延迟初始化,java,mockito,supplier,Java,Mockito,Supplier,我有一个类Sut,使用java.util.function.Supplier实现了延迟初始化。事实上,下面的代码更复杂,但这是Mockito无法测试的最简单形式。下面的测试抛出一个错误需要但未调用。。。但是,还有其他与此模拟的交互。为什么Mockito不计算调用create?代码流实际上进入了create();我用调试器检查过了 import java.util.function.Supplier; import static org.mockito.Mockito.spy; import s

我有一个类
Sut
,使用
java.util.function.Supplier
实现了延迟初始化。事实上,下面的代码更复杂,但这是Mockito无法测试的最简单形式。下面的测试抛出一个错误
需要但未调用。。。但是,还有其他与此模拟的交互
。为什么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();
        }
    }
}
导入java.util.function.Supplier;
导入静态org.mockito.mockito.spy;
导入静态org.mockito.mockito.times;
导入静态org.mockito.mockito.verify;
公共类测试时间{
@试验
public void testCreateOnlyOnce(){
Sut-Sut=spy(新的Sut());
sut.getData();
sut.getData();
sut.getData();
验证(sut,乘以(1)).create();
}
私有静态类Sut{
供应商数据=此::创建;
void getData(){
data.get();
}
对象创建(){
返回新对象();
}
}
}

首先,感谢您提出的书面问题

我自己测试过你的代码,看到了你提到的错误。虽然,我在调试时对您的代码做了一些更改。。。看一看:

    @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();
        }
    }
@测试
public void testCreateOnlyOnce(){
Sut-Sut=spy(新的Sut());
sut.getData();
sut.getData();
sut.getData();
验证(sut,乘以(1)).create();
}
私有静态类Sut{
私人供应商数据;
//在构造函数上添加了de数据对象初始化以帮助调试。
公共Sut(){
this.data=this::create;
}
void getData(){
data.get();
}
对象创建(){
返回新对象();
}
}
调试时我发现:

  • spy(new Sut())
    子句中正确调用了
    Sut
    类构造函数,但是没有在那里调用
    create()
    方法
  • 每次调用
    sut.getData()
    时,也会调用
    create()
    方法。是什么让我得出结论,最终
  • 在构造函数上,
    this::create
    所做的只是告诉java,每当它需要从供应商那里检索
    对象时,就会从
    create()
    方法检索
    对象。而且,供应商调用的
    create()
    方法来自不同于Mockito监视的类实例

    这就解释了为什么不能用verify跟踪它


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

    我想对Gabriel Pimenta的优秀答案补充一点。之所以如此,是因为mockito创建了spy
    new Sut()
    的浅拷贝,而您的
    Supplier
    引用了原始
    Sut
    实例中已编译的lambda方法,而不是spy实例

    另见和

    调试代码时,您可以看到其工作原理:

    Sut-Sut=spy(新的Sut())
    现在是
    Sut
    的模拟/间谍子类,作为实例
    TestTimes$Sut$MockitoMock$1381634547@5b202a3a
    。现在,原始
    新Sut()
    中的所有字段都被浅层复制,包括
    供应商数据。查看spy中的这个字段,我们可以看到它是
    TestTimes$Sut$$Lambda$1的一个实例/510109769@1ecee32c
    ,即指向原始
    Sut
    中的lambda。当我们在create方法中设置一个断点时,我们可以进一步观察到
    this
    指向
    TestTimes$Sut@232a7d73
    ,即原始的
    Sut
    ,而不是间谍实例

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

    • 将供应商注入您的
      Sut
      (在构建期间或作为
      getData
      的参数)
    • getData
      方法中惰性地创建供应商(以便它指向mockito实例)
    • 如果没有从外部传递供应商,则不要使用供应商,而是直接调用
      create

    我不知道答案,但我想说的是,这可能是我在这个网站上见过的最好的例子之一(最小、完整和可验证的例子)。