用mockito嘲笑一个单身人士

fbi*_*jec 26 java junit unit-testing mocking 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";
    }

}
Run Code Online (Sandbox Code Playgroud)

被测试的课程:

public class DriverSnapshotHandler {

    public String getImageURL() {
        return FormatterService.getInstance().formatTachoIcon();
    }

}
Run Code Online (Sandbox Code Playgroud)

单元测试:

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

    }

}
Run Code Online (Sandbox Code Playgroud)

我们的想法是配置可怕的单例的预期行为,因为被测试的类将调用它的getInstance然后调用formatTachoIcon方法.不幸的是,这失败并显示错误消息:

when() requires an argument which has to be 'a method call on a mock'.
Run Code Online (Sandbox Code Playgroud)

nos*_*ame 27

您要求的是不可能的,因为您的遗留代码依赖于静态方法getInstance()而Mockito不允许模拟静态方法,因此以下行不起作用

when(FormatterService.getInstance()).thenReturn(formatter);
Run Code Online (Sandbox Code Playgroud)

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

  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();
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

然后,您的测试应如下所示:

FormatterService formatter = mock(FormatterService.class);
when(formatter.formatTachoIcon()).thenReturn("MockedURL");
DriverSnapshotHandler handler = new DriverSnapshotHandler(formatter);
handler.getImageURL();
verify(formatter, atLeastOnce()).formatTachoIcon();
Run Code Online (Sandbox Code Playgroud)

  • 仅出于测试目的添加代码是一个好习惯吗?(驱动程序快照处理程序) (2认同)

小智 16

我认为这是可能的.查看如何测试单例的示例

测试前:

@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);
    }
}
Run Code Online (Sandbox Code Playgroud)

测试后 - 清理类非常重要,因为其他测试将与模拟实例混淆.

@After
public void resetSingleton() throws Exception {
   Field instance = FormatterService.class.getDeclaredField("instance");
   instance.setAccessible(true);
   instance.set(null, null);
}
Run Code Online (Sandbox Code Playgroud)

考试:

@Test
public void testFormatterServiceIsCalled() {
    DriverSnapshotHandler handler = new DriverSnapshotHandler();
    String url = handler.getImageURL();

    verify(formatter, atLeastOnce()).formatTachoIcon();
    assertEquals(MOCKED_URL, url);
}
Run Code Online (Sandbox Code Playgroud)


kya*_*kya 7

我只想从 noscreenname 完成解决方案。解决方案是使用 PowerMockito。因为 PowerMockito 可以做类似 Mockito 的事情,所以有时你可以只使用 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());
    }

}
Run Code Online (Sandbox Code Playgroud)

单例类:

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

}
Run Code Online (Sandbox Code Playgroud)

这是我的摇篮:

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

}
Run Code Online (Sandbox Code Playgroud)