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'.
If you are using Mockito 3.4. 0+, you may mock a singleton like the following, without PowerMock or other dependencies.
There is a way to mock Singleton. Use powermock to mock static method and use Whitebox to invoke constructor YourClass mockHelper = Whitebox . invokeConstructor(YourClass. class); Whitebox.
Allow a single instance of a Python application to run at once.
Option 2: Adding a file to enable the Mock maker inlineCreate a resources directory in your test directory if you do not have one. In resources create the directory mockito-extensions and in that directory the file org. mockito. plugins.
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:
Use a different mocking tool, such as PowerMock, that allows to mock static methods.
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();
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); }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With