用mockito嘲笑一个单身人士
我需要测试一些遗留代码,它在方法调用中使用单例。 测试的目的是确保clas sunder测试调用单例方法。 我在SO上看到过类似的问题,但所有的答案都需要其他依赖项(不同的测试框架) – 我不幸的是仅限于使用Mockito和JUnit,但这对于这样的流行框架来说应该是完全可能的。
单身人士:
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"; } }
被测试的课程:
public class DriverSnapshotHandler { public String getImageURL() { return FormatterService.getInstance().formatTachoIcon(); } }
unit testing:
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(); } }
我们的想法是配置可怕的单例的预期行为,因为被测试的类将调用它的getInstance然后调用formatTachoIcon方法。 不幸的是,这失败并显示错误消息:
when() requires an argument which has to be 'a method call on a mock'.
您要问的是不可能的,因为遗留代码依赖于静态方法getInstance()
而Mockito不允许模拟静态方法,因此以下行不起作用
when(FormatterService.getInstance()).thenReturn(formatter);
有两种解决此问题的方法:
-
使用允许模拟静态方法的其他模拟工具(如PowerMock)。
-
重构您的代码,以便您不依赖静态方法。 我能想到实现这一目标的最小侵入方法是向
DriverSnapshotHandler
添加一个构造函数,该构造函数注入FormatterService
依赖项。 此构造函数将仅用于测试,您的生产代码将继续使用真正的单例实例。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(); } }
然后,您的测试应如下所示:
FormatterService formatter = mock(FormatterService.class); when(formatter.formatTachoIcon()).thenReturn("MockedURL"); DriverSnapshotHandler handler = new DriverSnapshotHandler(formatter); handler.getImageURL(); verify(formatter, atLeastOnce()).formatTachoIcon();
我认为这是可能的。 查看如何测试单例的示例
测试前:
@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 public void resetSingleton() throws Exception { Field instance = FormatterService.class.getDeclaredField("instance"); instance.setAccessible(true); instance.set(null, null); }
考试:
@Test public void testFormatterServiceIsCalled() { DriverSnapshotHandler handler = new DriverSnapshotHandler(); String url = handler.getImageURL(); verify(formatter, atLeastOnce()).formatTachoIcon(); assertEquals(MOCKED_URL, url); }
你的getInstance Methos是静态的,因此无法使用mockito进行模拟。 http://cube-drone.com/media/optimized/172.png 。 您可能希望使用PowerMockito来执行此操作。 虽然我不建议这样做。 我会通过dependency injection来测试DriverSnapshotHandler:
public class DriverSnapshotHandler { private FormatterService formatterService; public DriverSnapshotHandler(FormatterService formatterService) { this.formatterService = formatterService; } public String getImageURL() { return formatterService.formatTachoIcon(); } }
unit testing:
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(); } }
您可能希望在@After方法中将mock设置为null。 这是更清洁的解决方案。