I have a class Sut
with lazy initialization implemented using java.util.function.Supplier
. In fact it's more complicated that the code below, but this is the simplest form that Mockito cannot test. The test below throws an error Wanted but not invoked ... However, there were other interactions with this mock
. Why doesn't Mockito count the invocation of create
? The code flow actually enters create()
; I checked that with debugger.
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();
}
}
}
First of all, thanks for the well written question.
I have tested your code myself and seen the error you mentioned. Although, I have changed your code a little bit while debugging... Take a look:
@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();
}
}
What I found out while debugging:
Sut
class constructor is being called correctly inside the spy(new Sut())
clause, but the create()
method is not being called there.sut.getData()
is called, the create()
method is also called. What made me conclude, finally that: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.
That explains why you cannot track it with verify.
EDIT: From my research, that is actually the desired behavior of the Supplier. It just creates an interface that has a get()
method that calls whatever noArgs method you declared on the method reference. Take a look at this on "Instantiate Supplier Using Method Reference".