用mockito嘲笑一个单身人士

时间:2016-08-12 09:23:51

标签: java unit-testing junit 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";
    }

}

受测试的课程:

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'.

7 个答案:

答案 0 :(得分:23)

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

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

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

  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();
        }
    }
    
  3. 然后,您的测试应如下所示:

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

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