使用 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();
}
}
调试时发现的:
- 在
spy(new Sut())
子句中正确调用了 Sut
class 构造函数,但未在那里调用 create()
方法。
- 每次调用
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
我有一个 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();
}
}
调试时发现的:
- 在
spy(new Sut())
子句中正确调用了Sut
class 构造函数,但未在那里调用create()
方法。 - 每次调用
sut.getData()
时,也会调用create()
方法。是什么让我得出结论,最终:
On the constructor, all that
this::create
did was telling java that, whenever it needs to retrieve theObject
from the supplier, thatObject
will be retrieved from thecreate()
method. And, thecreate()
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 方法,而不是间谍实例。
另见
调试代码时,您可以看到它是如何工作的:
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