用mockito嘲笑一个单体

2ekbmq32  于 2022-11-08  发布在  其他
关注(0)|答案(9)|浏览(167)

我需要测试一些遗留代码,它们在一个方法调用中使用了单例。测试的目的是确保类分离测试调用单例方法。我在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'.
3pmvbmvn

3pmvbmvn1#

您所要求的是不可能的,因为您的遗留代码依赖于一个静态方法getInstance(),而Mockito不允许模拟静态方法,因此下面这行代码将无法工作

when(FormatterService.getInstance()).thenReturn(formatter);

有两种方法可以解决此问题:
1.使用不同的模拟工具,例如PowerMock,它允许模拟静态方法。
1.重构你的代码,这样你就不依赖于静态方法了。我能想到的实现这一点的最小侵入性的方法是向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();
fv2wmkja

fv2wmkja2#

我认为这是可能的。请参阅示例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);
}
lfapxunr

lfapxunr3#

我只是想完成noscreenname的解决方案。解决方案是使用PowerMockito。因为PowerMockito可以做类似Mockito的事情,所以sometimes你可以直接使用PowerMockito。
示例代码如下所示:

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";
    }

}

这是我的Gradle:

/*

* 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**/

}
70gysomp

70gysomp4#

您的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,这是一个更简洁的解决方案。

mhd8tkvw

mhd8tkvw5#

如果您使用的是Mockito 3.4.0+,您可以像下面这样模拟一个单例,而不需要PowerMock或其他依赖项。
我有一个名为ProxyContext的单例:
第一个
此示例来自我之前提交的PR:https://github.com/apache/shardingsphere/pull/17796/files
您可以参考Mockito JavaDoc以了解更多详细信息:https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#48

h7appiyu

h7appiyu6#

这是我测试单例类的方法,你只需要模拟所有的单例类,然后使用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());
    }
}
hc8w905p

hc8w905p7#

我有一个使用反射来模拟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来获得想法。

huwehgph

huwehgph8#

您可以使用powermock/ reflection来更改示例变量本身的值。

FormatterService formatter = mock(FormatterService.class);
when(formatter.formatTachoIcon()).thenReturn("MockedURL");

// here use reflection or whitebox 

Whitebox.setInternalState(FormatterService.class, "INSTANCE", formatter);
r1zhe5dt

r1zhe5dt9#

在我看来,作为一个软件开发的初学者,可以在一个驱动类中使用依赖注入这样的机制来测试一个单例类。因为我们可以控制类的创建示例,并且仍然能够模拟静态方法。
此外,PowerMock是一个简单而干净的选项来模拟静态方法。但正如前面提到的,一个技巧,如使用单独的构造函数控制类的初始化,可以帮助实现这一点。然后在我们的测试中,我们可以传递mock类作为参数来初始化格式化程序服务。

public class DriverSnapshotHandler {
    private FormatterService formatter;
    public DriverSnapshotHandler() {
        this(FormatterService.getInstance());
    }
    public DriverSnapshotHandler (FormatterService formatterService){
           this.formatter = formatterService;
    }
    public String getImageURL() {
        return formatter.formatTachoIcon();
    }
}

//Mockito test for above

@Test
public void testGetUrl(){
  FormatterService formatter = mock(FormatterService.class);
  when(formatter.formatTachoIcon()).thenReturn("TestURL");
  DriverSnapshotHandler handler = new DriverSnapshotHandler(formatter);
  assertEquals(handler.getImageURL(), "TestUrl";
}

相关问题