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

有两种解决此问题的方法:

  1. 使用允许模拟静态方法的其他模拟工具(如PowerMock)。

  2. 重构您的代码,以便您不依赖静态方法。 我能想到实现这一目标的最小侵入方法是向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。 这是更清洁的解决方案。