Java 用mockito嘲弄单身汉
我需要测试一些遗留代码,它在方法调用中使用单例。该测试的目的是确保clas sunder测试调用Singleton方法。 我也看到过类似的问题,但所有的答案都需要其他依赖项(不同的测试框架)——不幸的是,我只限于使用Mockito和JUnit,但在这种流行的框架中,这应该是完全可能的 单身人士:Java 用mockito嘲弄单身汉,java,unit-testing,junit,mocking,mockito,Java,Unit Testing,Junit,Mocking,Mockito,我需要测试一些遗留代码,它在方法调用中使用单例。该测试的目的是确保clas sunder测试调用Singleton方法。 我也看到过类似的问题,但所有的答案都需要其他依赖项(不同的测试框架)——不幸的是,我只限于使用Mockito和JUnit,但在这种流行的框架中,这应该是完全可能的 单身人士: public class FormatterService { private static FormatterService INSTANCE; private Formatter
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();
}
}
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();
}
}
这个想法是为了配置可怕的单例的预期行为,因为被测试的类将调用它的getInstance,然后调用Formattachicon方法。不幸的是,此操作失败,并显示错误消息:
when() requires an argument which has to be 'a method call on a mock'.
您的getInstance方法是静态的,因此不能使用mockito进行模拟。您可能希望使用来执行此操作。虽然我不建议这样做。我将通过依赖项注入测试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(FormatterService.getInstance()).thenReturn(formatter);
when(formatter.formatTachoIcon()).thenReturn("MockedURL");
}
@Test
public void testFormatterServiceIsCalled() {
DriverSnapshotHandler handler = new DriverSnapshotHandler();
handler.getImageURL();
verify(formatter, atLeastOnce()).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。
这是IMHO更干净的解决方案。您所要求的是不可能的,因为您的遗留代码依赖于静态方法
getInstance()
,并且Mockito不允许模拟静态方法,因此下面的行将不起作用
when(FormatterService.getInstance()).thenReturn(formatter);
解决此问题有两种方法:
driversnapshotdhandler
添加一个构造函数,该构造函数注入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);
}
如果它能帮助某人
这是我测试单例类的方法
您只需要模拟所有的单例类,然后使用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());
}
}
我有一个使用反射模拟单例类的变通方法。在设置测试时,您可以考虑执行以下操作:p>
@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
由Android中的Robolectric
提供。但是,您可以编写自己的函数来帮助您实现这一点。你可以试着想出一个主意 在我看来,作为软件开发的初学者,在驱动程序/其他服务中注入单例类的依赖项是一个不错的选择。
因为我们可以控制类的单个实例的创建,并且仍然能够模拟静态方法(正如您可能已经猜到的,我脑海中有util服务),而无需使用PowerMock之类的东西来模拟静态方法(IME,这有点痛苦)
我非常愿意从可靠或良好的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";
}
我只想完成noscreenname的解决方案。解决方案是使用PowerMockito。因为PowerMockito可以做类似于Mockito的事情,所以您可以使用PowerMockito 示例代码如下所示:
import org.junit.Test;
导入org.junit.runner.RunWith;
导入org.powermock.api.mockito.PowerMockito;
导入org.powermock.core.classloader.annotations.PrepareForTest;
导入org.powermock.modules.junit4.PowerMockRunner;
导入java.lang.reflect.Field;
导入静态org.powermock.api.mockito.PowerMockito.mock;
导入静态org.powermock.api.mockito.PowerMockito.when;
@RunWith(PowerMockRunner.class)
@PrepareForTest({Singleton.class})
公共类单音测试{
@试验
公共无效测试_1(){
//创建一个模拟单例并进行更改
Singleton mock=mock(Singleton.class);
when(mock.dosth())。然后返回(“成功”);
System.out.println(mock.dosth());
//将该单例插入singleton.getInstance()中
mockStatic(Singleton.class);
when(Singleton.getInstance()).thenReturn(mock);
System.out.println(“结果:+Singleton.getInstance().dosth());
}
}
单身人士类别:
公共类单例{
私有静态单例实例;
私人单身人士(){
}
公共静态单例getInstance(){
if(实例==null){
INSTANCE=newsingleton();
}
返回实例;
}
公共字符串dosth(){
返回“失败”;
}
}
这是我的毕业证书:
/*
* version compatibility see: https://github.com/powermock/powermock/wiki/mockito
*
* */
def powermock='2.0.2'
def mockito='2.8.9'
...
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
/** mock **/
testCompile group: 'org.mockito', name: 'mockito-core', version: "${mockito}"
testCompile "org.powermock:powermock-core:${powermock}"
testCompile "org.powermock:powermock-module-junit4:${powermock}"
testCompile "org.powermock:powermock-api-mockito2:${powermock}"
/**End of power mock **/
}
在Mockito中,如果不使用PowerMock,就无法做到这一点,除非重构一个类。但我不知道你为什么要这么做。您正在用一行代码对一个方法进行单元测试,没有内部逻辑。这不能失败。测试的目的是确保clas sunder测试调用singletons方法。任何好的测试用例都不应该以这种方式为目的。相反,我们的目标是测试一些有意义的业务功能。模拟依赖项并验证调用了某个方法并不一定是错误的,但应该只在需要时进行。所有这些方法都会引起麻烦……它们适用于静态方法,但如果它是实例的方法,并且引用了类变量,它将获得空指针对吗?但powerMockito是一种方式。取而代之的是做我们以前做的事情,让我们的单例实现一个接口。然后在您的测试中使用接口,该接口中只有所有存根。参考:@fbielejec mock方法内部是什么?您需要在(FormatterService.getInstance())时删除行
。然后返回(formatter)
,否则测试不会运行。第三种方法是认识到(可能)在不模拟si的情况下可以编写更好的测试