mockito

Use cases interaction when using Mockito4.* to mock singleton


I write a singleTone named DemoSingleTon, a main class named DemoMain and a test class named DemoTest. when testing all tests of DemoTest individually, tests run successfully.If all tests run together, the latter two use cases will always fail.It looks like the mockStatic behind doesn't take effect.

enter image description here

public final class DemoSingleTon {
    private static final DemoSingleTon instance = new DemoSingleTon();

    private DemoSingleTon() {
    }

    public static DemoSingleTon getInstance() {
        return instance;
    }

    public String test(String input) {
        return input == null ? "" : input;
    }
}
public class DemoMain {

    private static final DemoSingleTon instance = DemoSingleTon.getInstance();

    public static String testInput() {
        return TestUtil.test("");
    }

    public String testInputUseSingleTone() {
        return instance.test("input1");
    }
}
public class DemoTest {

    @Test
    public void test() {
        try (MockedStatic<DemoSingleTon> mockedStatic = Mockito.mockStatic(DemoSingleTon.class)) {
            DemoSingleTon testUtil1 = Mockito.mock(DemoSingleTon.class);
            mockedStatic.when(DemoSingleTon::getInstance).thenReturn(testUtil1);
            Mockito.when(testUtil1.test("input1")).thenReturn("nothing");
            DemoMain demoMain = new DemoMain();
            assertEquals("nothing", demoMain.testInputUseSingleTone());
        }
    }

    @Test
    public void test1() {
        try (MockedStatic<DemoSingleTon> mockedStatic = Mockito.mockStatic(DemoSingleTon.class)) {
            DemoSingleTon testUtil1 = Mockito.mock(DemoSingleTon.class);
            mockedStatic.when(DemoSingleTon::getInstance).thenReturn(testUtil1);
            Mockito.when(testUtil1.test("input1")).thenReturn("everything");
            DemoMain demoMain = new DemoMain();
            assertEquals("everything", demoMain.testInputUseSingleTone());
        }
    }

    @Test
    public void test2() {
        DemoMain demoMain = new DemoMain();
        assertEquals("input1", demoMain.testInputUseSingleTone());

    }
}

build.gradle following:

testImplementation group: 'org.mockito', name: 'mockito-inline', version: '4.9.0'
testImplementation('org.junit.jupiter:junit-jupiter-api:5.6.2')
testImplementation('org.junit.jupiter:junit-jupiter-engine:5.6.2')

I think each call to mockitoStatic should be independent and not interact with each other.


Solution

  • public class DemoMain {
        private static final DemoSingleTon instance = DemoSingleTon.getInstance();
    }
    

    executes DemoSingleton.getInstance() only once the first time the class is loaded and assigns the reference to the instance field. Once that has happened, your static mock cannot intercept the method call anymore, because the method is never called again. Loading the class happens on first access to the class, meaning:

            try (MockedStatic<DemoSingleTon> mockedStatic = Mockito.mockStatic(DemoSingleTon.class)) {
                DemoSingleTon testUtil1 = Mockito.mock(DemoSingleTon.class);
                mockedStatic.when(DemoSingleTon::getInstance).thenReturn(testUtil1);
                Mockito.when(testUtil1.test("input1")).thenReturn("nothing");
    
                DemoMain demoMain = new DemoMain(); // <-- class loaded here and DemoSingleton.getInstance is called (which is intercepted by your mock)
    
                assertEquals("nothing", demoMain.testInputUseSingleTone());
            }
    

    The behavior would be different if you changed your main class implementation to:

    public class DemoMain {
        public String testInputUseSingleTone() {
            return DemoSingleTon.getInstance().test("input1");
        }
    }
    

    Because now, DemoSingleton.getInstance() is called every time you call your method and thus can be intercepted by the static mock.

    In essence, this question is another variation of Why is my class not calling my mocked methods in unit test?, albeit a little trickier. Do yourself a favor and take a step back to think about how you can make your design more testable, without requiring static mocks.