fbielejec fbielejec - 3 months ago 108
Java Question

Mocking a singleton with mockito

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

Answer

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