我需要测试一些遗留代码,它在方法调用中使用单例。测试的目的是确保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();
}
}
单元测试:
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'.
答案 0 :(得分:23)
您要求的是不可能的,因为您的遗留代码依赖于静态方法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();
答案 1 :(得分:11)
我认为这是可能的。查看示例how to test a singleton
在考试之前:
@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);
}
答案 2 :(得分:1)
您的getInstance方法是静态的,因此无法使用mockito进行模拟。 http://cube-drone.com/media/optimized/172.png。您可能希望使用PowerMockito来执行此操作。虽然我不建议这样做。我会通过依赖注入来测试DriverSnapshotHandler:
public class DriverSnapshotHandler {
private FormatterService formatterService;
public DriverSnapshotHandler(FormatterService formatterService) {
this.formatterService = formatterService;
}
public String getImageURL() {
return formatterService.formatTachoIcon();
}
}
单元测试:
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。 这是恕我直言的清洁解决方案。
答案 3 :(得分:1)
我只想从noscreenname完成解决方案。解决方案是使用PowerMockito。由于PowerMockito可以执行类似Mockito的操作,因此sometimes您可以只使用PowerMockito。
示例代码在这里:
public Flux<PostSummary> findByTitleLike(String title, Pageable pageable);
单班:
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());
}
}
这是我的摇篮:
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";
}
}
答案 4 :(得分:0)
如果它可以帮助某人 这是我测试单例类的方法 您只需要模拟所有单例类,然后使用doCallRealMethod真正调用要测试的方法即可。
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());
}
}
答案 5 :(得分:0)
我有一个解决方法,可以使用反射来模拟Singleton类。在设置测试时,您可以考虑执行以下操作。
@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.
}
ReflectionHelpers
由Robolectric
在Android中提供。但是,您始终可以编写自己的函数来帮助您。您可以check the question here来获得想法。
答案 6 :(得分:0)
作为IMO软件开发的初学者,在驱动程序/其他服务中单例类的依赖项注入是一个不错的选择。 由于我们可以控制类的单个实例的创建,并且仍然能够模拟静态方法(您可能已经猜到了,我脑海中有util服务),而无需使用PowerMock之类的东西来模拟静态方法(IME有点痛苦) 我非常愿意从 SOLID 或 Good OO设计原则的角度来听取经验丰富的人的意见。
public class DriverSnapshotHandler {
private FormatterService formatter;
public DriverSnapshotHandler() {
this(FormatterService.getInstance());
}
public DriverSnapshotHandler (FormatterService formatterService){
this.formatter = formatterService;
}
public String getImageURL() {
return FormatterService.getInstance().formatTachoIcon();
}
}
and then test using Mockito, something like this.
@Test
public void testGetUrl(){
FormatterService formatter = mock(FormatterService.class);
when(formatter.formatTachoIcon()).thenReturn("TestURL");
DriverSnapshotHandler handler = new DriverSnapshotHandler(formatter);
assertEquals(handler.getImageURL(), "TestUrl";
}