53

I need to test some legacy code, which uses a singleton in a a method call. The purpose of the test is to ensure that the clas sunder test makes a call to singletons method. I have seen similar questions on SO, but all the answers require other dependencies (different test frameworks) - I'm unfortunately limited to using Mockito and JUnit, but this should be perfectly possible with such popular framework.

The singleton:

public class FormatterService {

    private static FormatterService INSTANCE;

    private FormatterService() {
    }

    public static FormatterService getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new FormatterService();
        }
        return INSTANCE;
    }

    public String formatTachoIcon() {
        return "URL";
    }

}

The class under test:

public class DriverSnapshotHandler {

    public String getImageURL() {
        return FormatterService.getInstance().formatTachoIcon();
    }

}

The unit test:

public class TestDriverSnapshotHandler {

    private FormatterService formatter;

    @Before
    public void setUp() {

        formatter = mock(FormatterService.class);

        when(FormatterService.getInstance()).thenReturn(formatter);

        when(formatter.formatTachoIcon()).thenReturn("MockedURL");

    }

    @Test
    public void testFormatterServiceIsCalled() {

        DriverSnapshotHandler handler = new DriverSnapshotHandler();
        handler.getImageURL();

        verify(formatter, atLeastOnce()).formatTachoIcon();

    }

}

The idea was to configure the expected behaviour of the dreaded singleton, since the class under test will call it's getInstance and then formatTachoIcon methods. Unfortunately this fails with an error message:

when() requires an argument which has to be 'a method call on a mock'.
fbielejec
  • 3,492
  • 4
  • 27
  • 35
  • 1
    You can't do this in Mockito, without also using PowerMock, unless you refactor one of your classes. But I'm not sure why you want to. You're unit testing a method with just one line, and no internal logic. This can't fail. Spend your testing effort elsewhere. – Dawood ibn Kareem Aug 12 '16 at 09:37
  • 1
    "The purpose of the test is to ensure that the clas sunder test makes a call to singletons method." No good test case should have this kind of thing as its purpose. Instead, aim to test some meaningful business functionality. Mocking a dependency and verifying a method is called is not necessarily wrong, but it should be done only when needed. – Rogério Aug 12 '16 at 15:03
  • all these ways just cause headaches...they work for static methods but if its a method of the instance and its referencing a class variable it will get null pointer right ? but powerMockito is one way. instead just do what do we in the old days, make y our singleTon implement an interface. then in your test use the interface which just has all stubs in it. reference: https://stackoverflow.com/a/17325647/835883 – j2emanue Feb 07 '20 at 14:38
  • @fbielejec what is inside mock method? – codeSeeker Jan 01 '21 at 08:09

9 Answers9

45

What you are asking is not possible because your legacy code relies on a static method getInstance() and Mockito does not allow to mock static methods, so the following line won't work

when(FormatterService.getInstance()).thenReturn(formatter);

There are 2 ways around this problem:

  1. Use a different mocking tool, such as PowerMock, that allows to mock static methods.

  2. Refactor your code, so that you don't rely on the static method. The least invasive way I can think of to achieve this is by adding a constructor to DriverSnapshotHandler that injects a FormatterService dependency. This constructor will be only used in tests and you production code will continue to use the real singleton instance.

    public static class DriverSnapshotHandler {
    
        private final FormatterService formatter;
    
        //used in production code
        public DriverSnapshotHandler() {
            this(FormatterService.getInstance());
        }
    
        //used for tests
        DriverSnapshotHandler(FormatterService formatter) {
            this.formatter = formatter;
        }
    
        public String getImageURL() {
            return formatter.formatTachoIcon();
        }
    }
    

Then, your test should look like this :

FormatterService formatter = mock(FormatterService.class);
when(formatter.formatTachoIcon()).thenReturn("MockedURL");
DriverSnapshotHandler handler = new DriverSnapshotHandler(formatter);
handler.getImageURL();
verify(formatter, atLeastOnce()).formatTachoIcon();
pkramaric
  • 165
  • 2
  • 13
noscreenname
  • 3,314
  • 22
  • 30
  • A third way would be to recognize that (probably) a better test can be written without mocking the singleton. We can't know for sure in this case, since it's a [XY question](http://xyproblem.info). – Rogério Aug 12 '16 at 15:06
  • 2
    Is it good practice to add code just for testing purpose ? (DriverSnapshotHandler) – Shaaban Ebrahim Sep 10 '20 at 20:34
36

I think it is possible. See an example how to test a singleton

Before a test:

@Before
public void setUp() {
    formatter = mock(FormatterService.class);
    setMock(formatter);
    when(formatter.formatTachoIcon()).thenReturn(MOCKED_URL);
}

private void setMock(FormatterService mock) {
    try {
        Field instance = FormatterService.class.getDeclaredField("instance");
        instance.setAccessible(true);
        instance.set(instance, mock);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

After the test - it is important to clean up the class, because other tests will be confused with the mocked instance.

@After
public void resetSingleton() throws Exception {
   Field instance = FormatterService.class.getDeclaredField("instance");
   instance.setAccessible(true);
   instance.set(null, null);
}

The test:

@Test
public void testFormatterServiceIsCalled() {
    DriverSnapshotHandler handler = new DriverSnapshotHandler();
    String url = handler.getImageURL();

    verify(formatter, atLeastOnce()).formatTachoIcon();
    assertEquals(MOCKED_URL, url);
}
Kyrylo Semenko
  • 866
  • 9
  • 11
7

I just want to complete the solution from noscreenname. The solution is using PowerMockito. Because PowerMockito can do something like Mockito, So sometimes you can just use PowerMockito .

The example code is here:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;


import java.lang.reflect.Field;

import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.when;

@RunWith(PowerMockRunner.class)
@PrepareForTest({Singleton.class})
public class SingletonTest {

    @Test
    public void test_1() {
        // create a mock singleton and change
        Singleton mock = mock(Singleton.class);
        when(mock.dosth()).thenReturn("succeeded");
        System.out.println(mock.dosth());

        // insert that singleton into Singleton.getInstance()
        PowerMockito.mockStatic(Singleton.class);
        when(Singleton.getInstance()).thenReturn(mock);
        System.out.println("result:" + Singleton.getInstance().dosth());
    }

}

Singleton class:

public class Singleton {

    private static Singleton INSTANCE;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }

    public String dosth() {
        return "failed";
    }

}

Here is my Gradle:

/*
*  version compatibility see: https://github.com/powermock/powermock/wiki/mockito
*
* */

def powermock='2.0.2'
def mockito='2.8.9'
...
dependencies {
    testCompile group: 'junit', name: 'junit', version: '4.12'

    /** mock **/
    testCompile group: 'org.mockito', name: 'mockito-core', version: "${mockito}"

    testCompile "org.powermock:powermock-core:${powermock}"
    testCompile "org.powermock:powermock-module-junit4:${powermock}"
    testCompile "org.powermock:powermock-api-mockito2:${powermock}"
    /**End of power mock **/

}
Jess Chen
  • 3,136
  • 1
  • 26
  • 35
3

If you are using Mockito 3.4.0+, you may mock a singleton like the following, without PowerMock or other dependencies.

I have a singleton named ProxyContext:

public final class ProxyContext {
    
    private static final ProxyContext INSTANCE = new ProxyContext();

    // Other fields and methods are omitted...

    public static ProxyContext getInstance() {
        return INSTANCE;
    }
}
try (MockedStatic<ProxyContext> mocked = mockStatic(ProxyContext.class)) {
    ProxyContext mockedProxyContext = mock(ProxyContext.class);
    mocked.when(ProxyContext::getInstance).thenReturn(mockedProxyContext);
    when(mockedProxyContext.methodYouWantToMock()).thenReturn(Collections.emptySet());
    // Do the testing stuff...
}

This example is from a PR I submitted before: https://github.com/apache/shardingsphere/pull/17796/files

You may refer to Mockito JavaDoc for more details: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#48

Wu Weijie
  • 301
  • 3
  • 7
1

Your getInstance Method is static, thus can't be mocked using mockito. http://cube-drone.com/media/optimized/172.png. You might want to use PowerMockito to do so. Although I would not recommend doing it this way. I would test DriverSnapshotHandler via dependency injection:

public class DriverSnapshotHandler {

    private FormatterService formatterService;

    public DriverSnapshotHandler(FormatterService formatterService) {
        this.formatterService = formatterService;
    }

    public String getImageURL() {
        return formatterService.formatTachoIcon();
    }

}

The unit test:

public class TestDriverSnapshotHandler {

    private FormatterService formatter;

    @Before
    public void setUp() {

        formatter = mock(FormatterService.class);

        when(formatter.formatTachoIcon()).thenReturn("MockedURL");

    }

    @Test
    public void testFormatterServiceIsCalled() {

        DriverSnapshotHandler handler = new DriverSnapshotHandler(formatter);
        handler.getImageURL();

        verify(formatter, times(1)).formatTachoIcon();

    }

}

You might want to set the mock to null in a @After method. This is IMHO the cleaner solution.

Nicholas K
  • 15,148
  • 7
  • 31
  • 57
  • 1
    You need to remove the line `when(FormatterService.getInstance()).thenReturn(formatter)`, otherwise the test wont run – noscreenname Aug 12 '16 at 10:01
0

If it could helps someone This is my method to test singleton classes You just need to mock all your singleton class and then use doCallRealMethod to really call methods you want to test.

SingletonClass.java :

class SingletonClass {

    private static SingletonClass sInstance;

    private SingletonClass() {
        //do somethings
    }

    public static synchronized SingletonClass getInstance() {
        if (sInstance == null) {
            sInstance = new SingletonClass();
        }

        return sInstance;
    }

    public boolean methodToTest() {
        return true;
    }
}

SingletonClassTest.java :

import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;

import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;

public class SingletonClassTest {

    private SingletonClass singletonObject;

    @Before
    public void setUp() throws Exception {
        singletonObject = mock(SingletonClass.class);

        Mockito.doCallRealMethod().when(singletonObject).methodToTest();
    }

    @Test
    public void testMethodToTest() {
        assertTrue(singletonObject.methodToTest());
    }
}
majdus
  • 39
  • 4
0

I have a workaround for mocking a Singleton class using reflection. While setting up your tests, you might consider doing the following.

@Mock 
private MySingletonClass mockSingleton;

private MySingletonClass originalSingleton;

@Before 
public void setup() {
    originalSingleton = MySingletonClass.getInstance();
    when(mockSingleton.getSomething()).thenReturn("Something"); // Use the mock to return some mock value for testing

    // Now set the instance with your mockSingleton using reflection 
    ReflectionHelpers.setStaticField(MySingletonClass.class, "instance", mockSingleton);
}

@After
public void tearDown() {
    // Reset the singleton object when the test is complete using reflection again
    ReflectionHelpers.setStaticField(MySingletonClass.class, "instance", null);
}

@Test
public void someTest() {
    // verify something here inside your test function.
}

The ReflectionHelpers is provided by Robolectric in Android. However, you can always write your own functions which can help you with this. You can check the question here to get an idea.

Reaz Murshed
  • 23,691
  • 13
  • 78
  • 98
0

In my opinion as a beginner in Software Development, a mechanism like dependency injection can be used in a driver class to test a singleton class.As we can control creation instance of the class and still be able to mock the static methods.

Also, PowerMock is an easy and a cleaner option to mock the static methods. But as noted earlier, a trick like controlling the initialisation of class using separate constructors can help achieve this. And then in our tests, we can pass on the mock class as argument to initialise the Formatter Service.

public class DriverSnapshotHandler {
    private FormatterService formatter;
    public DriverSnapshotHandler() {
        this(FormatterService.getInstance());
    }
    public DriverSnapshotHandler (FormatterService formatterService){
           this.formatter = formatterService;
    }
    public String getImageURL() {
        return formatter.formatTachoIcon();
    }
}

//Mockito test for above

@Test
public void testGetUrl(){
  FormatterService formatter = mock(FormatterService.class);
  when(formatter.formatTachoIcon()).thenReturn("TestURL");
  DriverSnapshotHandler handler = new DriverSnapshotHandler(formatter);
  assertEquals(handler.getImageURL(), "TestUrl";
}
  • Your `getImageURL()` should use `formatter.getInstance().formatTachoIcon();` instead of `FormatterService...` – Ambareesh Apr 01 '22 at 17:35
0

You can use powermock/ reflection to change the value of instance variable itself.

FormatterService formatter = mock(FormatterService.class);
when(formatter.formatTachoIcon()).thenReturn("MockedURL");

// here use reflection or whitebox 

Whitebox.setInternalState(FormatterService.class, "INSTANCE", formatter);
zysaaa
  • 1,777
  • 2
  • 8
  • 19