Издевательская надпись с мокето

Мне нужно протестировать некоторый унаследованный код, который использует одноэлемент в вызове метода. Целью теста является обеспечение того, чтобы тест 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());
    }
}