Mockito-Java8 — это набор дополнений Mockito, использующих Java 8 и лямбда-выражения, которые делают Mockito еще более компактным.
В начале 2015 года я выступил с докладом по флеш-памяти. Java 8 дает возможность тестировать! на GeeCON TDD 2015 и DevConf.cz 2015. В своей речи на 4 примерах я показал, как Java 8, а именно лямбда-выражения, может упростить инструменты тестирования и тестирования в целом. Одним из таких инструментов был Mokcito. Чтобы не позволить моему PoC-коду умереть на слайдах и сделать его просто доступным для других, я выпустил небольшой проект с двумя полезными в указанном случае дополнениями Java 8 для Mockito.
Быстрое введение
В качестве предпосылки предположим, что у нас есть следующая структура данных:
@Immutable
class ShipSearchCriteria {
int minimumRange;
int numberOfPhasers;
}
и класс, который мы хотим заглушить / издеваться:
public class TacticalStation {
public int findNumberOfShipsInRangeByCriteria(
ShipSearchCriteria searchCriteria) { ... }
}
Библиотека предоставляет два дополнения:
Lambda matcher — позволяет определять логику соответствия в лямбда-выражении.
given(ts.findNumberOfShipsInRangeByCriteria(
argLambda(sc -> sc.getMinimumRange() > 1000))).willReturn(4);
Argument Captor — Java 8 edition — позволяет использовать `ArgumentCaptor` в одну строку (здесь с AssertJ):
verify(ts).findNumberOfShipsInRangeByCriteria(
assertArg(sc -> assertThat(sc.getMinimumRange()).isLessThan(2000)));
Лямбда-матчер
С помощью статического метода argLambda создается экземпляр лямбда-сопоставителя, который можно использовать для определения логики сопоставления в лямбда-выражении (здесь для заглушки). Это может быть особенно полезно при работе со сложными классами, передаваемыми в качестве аргумента.
@Test
public void shouldAllowToUseLambdaInStubbing() {
//given
given(ts.findNumberOfShipsInRangeByCriteria(
argLambda(sc -> sc.getMinimumRange() > 1000))).willReturn(4);
//expect
assertThat(ts.findNumberOfShipsInRangeByCriteria(
new ShipSearchCriteria(1500, 2))).isEqualTo(4);
//expect
assertThat(ts.findNumberOfShipsInRangeByCriteria(
new ShipSearchCriteria(700, 2))).isEqualTo(0);
}
Для сравнения та же логика, реализованная с пользовательским Ответом в Java 7:
@Test
public void stubbingWithCustomAsnwerShouldBeLonger() { //old way
//given
given(ts.findNumberOfShipsInRangeByCriteria(any())).willAnswer(new Answer<Integer>() {
@Override
public Integer answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
ShipSearchCriteria criteria = (ShipSearchCriteria) args[0];
if (criteria.getMinimumRange() > 1000) {
return 4;
} else {
return 0;
}
}
});
//expect
assertThat(ts.findNumberOfShipsInRangeByCriteria(
new ShipSearchCriteria(1500, 2))).isEqualTo(4);
//expect
assertThat(ts.findNumberOfShipsInRangeByCriteria(
new ShipSearchCriteria(700, 2))).isEqualTo(0);
}
Даже Java 8 и менее читаемые конструкции не слишком помогают:
@Test
public void stubbingWithCustomAsnwerShouldBeLongerEvenAsLambda() { //old way
//given
given(ts.findNumberOfShipsInRangeByCriteria(any())).willAnswer(invocation -> {
ShipSearchCriteria criteria = (ShipSearchCriteria) invocation.getArguments()[0];
return criteria.getMinimumRange() > 1000 ? 4 : 0;
});
//expect
assertThat(ts.findNumberOfShipsInRangeByCriteria(
new ShipSearchCriteria(1500, 2))).isEqualTo(4);
//expect
assertThat(ts.findNumberOfShipsInRangeByCriteria(
new ShipSearchCriteria(700, 2))).isEqualTo(0);
}
Аргумент Captor — Java 8 издание
Статический метод assertArg создает сопоставление аргументов, реализация которого внутренне использует ArgumentMatcher с утверждением, предоставленным в лямбда-выражении. В приведенном ниже примере AssertJ предоставляет значимое сообщение об ошибке, но можно использовать любые утверждения (например, native от TestNG или JUnit) (если это действительно необходимо). Это позволяет встроить ArgumentCaptor:
@Test
public void shouldAllowToUseAssertionInLambda() {
//when
ts.findNumberOfShipsInRangeByCriteria(searchCriteria);
//then
verify(ts).findNumberOfShipsInRangeByCriteria(
assertArg(sc -> assertThat(sc.getMinimumRange()).isLessThan(2000)));
}
По сравнению с 3 линиями классическим способом:
@Test
public void shouldAllowToUseArgumentCaptorInClassicWay() { //old way
//when
ts.findNumberOfShipsInRangeByCriteria(searchCriteria);
//then
ArgumentCaptor<ShipSearchCriteria> captor =
ArgumentCaptor.forClass(ShipSearchCriteria.class);
verify(ts).findNumberOfShipsInRangeByCriteria(captor.capture());
assertThat(captor.getValue().getMinimumRange()).isLessThan(2000);
}
Резюме
Представленные дополнения были созданы как PoC для моей речи на конференции, но должны быть полностью функциональными и потенциально полезными в конкретных случаях. Чтобы использовать его в своем проекте, достаточно использовать Mockito 1.10.x или 2.0.x-beta, добавить `mockito-java8` в качестве зависимости и, конечно, скомпилировать ваш проект с Java 8+.
Более подробная информация доступна на веб-странице проекта: https://github.com/szpak/mockito-java8