it-swarm.com.ru

Дразнить синглтона с мокито

Мне нужно протестировать некоторый унаследованный код, который использует одиночный код в вызове метода. Цель теста - убедиться, что тест 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'.
18
fbielejec

То, что вы спрашиваете, невозможно, потому что ваш унаследованный код использует статический метод getInstance(), а Mockito не позволяет имитировать статические методы, поэтому следующая строка не будет работать

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

Есть 2 способа обойти эту проблему:

  1. Используйте другой инструмент для насмешек, например PowerMock, который позволяет имитировать статические методы. 

  2. Рефакторинг вашего кода, чтобы вы не полагались на статический метод. Наименее инвазивный способ, которым я могу придумать, - это добавить конструктор в 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();
19
noscreenname

Я думаю, что это возможно. Смотрите пример как проверить синглтон

Перед тестом:

@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);
}
8
Kyrylo Semenko

Ваш getInstance Methos статичен, поэтому его нельзя смоделировать с помощью 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();

    }

}

Возможно, вы захотите установить для макета значение null в методе @After . Это imho более чистое решение.

1
Florian Biesinger