javamockitosupplier

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


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();
        }
    }
}

Solution

  • 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:

    1. The Sut class constructor is being called correctly inside the spy(new Sut()) clause, but the create() method is not being called there.
    2. Every time 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 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.

    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".