Издевательская надпись с мокето
Мне нужно протестировать некоторый унаследованный код, который использует одноэлемент в вызове метода. Целью теста является обеспечение того, чтобы тест 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();
}
}
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();
}
}
Идея состояла в том, чтобы настроить ожидаемое поведение страшного синглтона, так как тестируемый класс будет называть его getInstance, а затем formatTachoIcon. К сожалению, это не удается с сообщением об ошибке:
when() requires an argument which has to be 'a method call on a mock'.
Ответы
Ответ 1
То, что вы спрашиваете, невозможно, потому что ваш старый код использует статический метод getInstance()
, а Mockito не позволяет издеваться над статическими методами, поэтому следующая строка не будет работать
when(FormatterService.getInstance()).thenReturn(formatter);
Существует 2 способа решения этой проблемы:
-
Используйте другой инструмент для издевательств, например PowerMock, который позволяет имитировать статические методы.
-
Восстановите свой код, чтобы вы не полагались на статический метод. Наименее инвазивным способом, который я могу придумать для достижения этого, является добавление конструктора к DriverSnapshotHandler
, который вводит зависимость FormatterService
. Этот конструктор будет использоваться только в тестах, и ваш производственный код будет продолжать использовать реальный экземпляр singleton.
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();
Ответ 2
Я думаю, что это возможно. Посмотрите пример, как проверить синглтон
Перед тестом:
@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);
}
Ответ 3
Ваш метод 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();
}
}
Unit тест:
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();
}
}
Возможно, вы захотите установить для макета значение null в методе @After. Это ИМХО более чистое решение.
Ответ 4
Если бы это могло помочь кому-то
Это мой метод для тестирования одноэлементных классов
Вам просто нужно смоделировать весь ваш синглтон-класс и затем использовать 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());
}
}