Java 用 mockito 模拟单身人士

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/38914433/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 20:44:58  来源:igfitidea点击:

Mocking a singleton with mockito

javaunit-testingjunitmockingmockito

提问by fbielejec

I need to test some legacy code, which uses a singleton in a a method call. The purpose of the test is to ensure that the clas sunder test makes a call to singletons method. I have seen similar questions on SO, but all the answers require other dependencies (different test frameworks) - I'm unfortunately limited to using Mockito and JUnit, but this should be perfectly possible with such popular framework.

我需要测试一些遗留代码,它在方法调用中使用单例。测试的目的是确保 clas sunder 测试调用单例方法。我在 SO 上看到过类似的问题,但是所有的答案都需要其他依赖项(不同的测试框架)——不幸的是,我只能使用 Mockito 和 JUnit,但是对于这种流行的框架,这应该是完全可能的。

The singleton:

单身人士:

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

}

The class under test:

被测类:

public class DriverSnapshotHandler {

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

}

The unit test:

单元测试:

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

    }

}

The idea was to configure the expected behaviour of the dreaded singleton, since the class under test will call it's getInstance and then formatTachoIcon methods. Unfortunately this fails with an error message:

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

when() requires an argument which has to be 'a method call on a mock'.

采纳答案by noscreenname

What you are asking is not possible because your legacy code relies on a static method getInstance()and Mockito does not allow to mock static methods, so the following line won't work

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

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

There are 2 ways around this problem:

有两种方法可以解决这个问题:

  1. Use a different mocking tool, such as PowerMock, that allows to mock static methods.

  2. Refactor your code, so that you don't rely on the static method. The least invasive way I can think of to achieve this is by adding a constructor to DriverSnapshotHandlerthat injects a FormatterServicedependency. This constructor will be only used in tests and you production code will continue to use the real singleton instance.

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

Then, your test should look like this :

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

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

回答by Florian Biesinger

Your getInstance Method is static, thus can't be mocked using mockito. http://cube-drone.com/media/optimized/172.png. You might want to use PowerMockitoto do so. Although I would not recommend doing it this way. I would test DriverSnapshotHandler via dependency injection:

您的 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();
    }

}

The unit test:

单元测试:

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

    }

}

You might want to set the mock to null in a @After method. This is IMHO the cleaner solution.

您可能希望在 @After 方法中将模拟设置为 null。恕我直言,这是更清洁的解决方案。

回答by Kyrylo Semenko

I think it is possible. See an example how to test a singleton

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

Before a test:

测试前:

@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 the test - it is important to clean up the class, because other tests will be confused with the mocked instance.

测试之后 - 清理类很重要,因为其他测试会与模拟实例混淆。

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

The test:

考试:

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

    verify(formatter, atLeastOnce()).formatTachoIcon();
    assertEquals(MOCKED_URL, url);
}

回答by majdus

If it could helps someone This is my method to test singleton classes You just need to mock all your singleton class and then use doCallRealMethod to really call methods you want to test.

如果它可以帮助某人这是我测试单例类的方法你只需要模拟你所有的单例类,然后使用 doCallRealMethod 来真正调用你想要测试的方法。

SingletonClass.java :

单例类.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 :

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

回答by Reaz Murshed

I have a workaround for mocking a Singleton class using reflection. While setting up your tests, you might consider doing the following.

我有一个使用反射模拟单例类的解决方法。在设置测试时,您可以考虑执行以下操作。

@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.
}

The ReflectionHelpersis provided by Robolectricin Android. However, you can always write your own functions which can help you with this. You can check the question hereto get an idea.

ReflectionHelpers由设置Robolectric在Android系统。但是,您始终可以编写自己的函数来帮助您解决这个问题。您可以在此处查看问题以获得想法。