Есть ли способ захвата списка определенного типа с помощью mockitos ArgumentCaptore. Это не работает:
ArgumentCaptor<ArrayList<SomeType>> argument = ArgumentCaptor.forClass(ArrayList.class);
Есть ли способ захвата списка определенного типа с помощью mockitos ArgumentCaptore. Это не работает:
ArgumentCaptor<ArrayList<SomeType>> argument = ArgumentCaptor.forClass(ArrayList.class);
Ответы:
Вложенной непатентованной проблемы можно избежать с помощью аннотации @Captor :
public class Test{
@Mock
private Service service;
@Captor
private ArgumentCaptor<ArrayList<SomeType>> captor;
@Before
public void init(){
MockitoAnnotations.initMocks(this);
}
@Test
public void shouldDoStuffWithListValues() {
//...
verify(service).doStuff(captor.capture()));
}
}
MockitoAnnotations.initMocks(this)
в @Before
методе, а не использовать бегуна, который исключает возможность использовать другого бегуна. Тем не менее, +1, спасибо за указание на аннотацию.
Да, это общая проблема дженериков, а не специфическая для мокито.
Для объекта нет объекта класса ArrayList<SomeType>
, и, таким образом, вы не можете безопасно передавать этот объект методу, требующему a Class<ArrayList<SomeType>>
.
Вы можете привести объект к нужному типу:
Class<ArrayList<SomeType>> listClass =
(Class<ArrayList<SomeType>>)(Class)ArrayList.class;
ArgumentCaptor<ArrayList<SomeType>> argument = ArgumentCaptor.forClass(listClass);
Это выдаст несколько предупреждений о небезопасных приведениях, и, конечно, ваш ArgumentCaptor не сможет по-настоящему провести различие между ArrayList<SomeType>
и ArrayList<AnotherType>
без проверки элементов.
(Как уже упоминалось в другом ответе, хотя это общая проблема общего характера, для аннотации существует специфическое для Mockito решение проблемы безопасности типов @Captor
. Он по-прежнему не может различить ArrayList<SomeType>
и ArrayList<OtherType>
.)
Взгляните также на комментарий Тэнши . Вы можете изменить исходный код с Paŭlo Ebermann на этот (гораздо проще)
final ArgumentCaptor<List<SomeType>> listCaptor
= ArgumentCaptor.forClass((Class) List.class);
ArgumentCaptor<List<SimeType>> argument = ArgumentCaptor.forClass((Class) List.class);
@SuppressWarnings("unchecked")
аннотацию над строкой определения захвата аргумента. Кроме того, приведение к Class
излишним.
Class
не является лишним в моих тестах.
Если вы не боитесь старой семантики в стиле java (не являющейся типичной безопасностью), это также работает и достаточно просто:
ArgumentCaptor<List> argument = ArgumentCaptor.forClass(List.class);
verify(subject.method(argument.capture()); // run your code
List<SomeType> list = argument.getValue(); // first captured List, etc.
List<String> mockedList = mock(List.class);
List<String> l = new ArrayList();
l.add("someElement");
mockedList.addAll(l);
ArgumentCaptor<List> argumentCaptor = ArgumentCaptor.forClass(List.class);
verify(mockedList).addAll(argumentCaptor.capture());
List<String> capturedArgument = argumentCaptor.<List<String>>getValue();
assertThat(capturedArgument, hasItem("someElement"));
Основываясь на комментариях @ tenhi и @ pkalinow (также благодарности @rogerdpack), ниже приводится простое решение для создания захватчика аргументов списка, который также отключает предупреждение «использует непроверенные или небезопасные операции» :
@SuppressWarnings("unchecked")
final ArgumentCaptor<List<SomeType>> someTypeListArgumentCaptor =
ArgumentCaptor.forClass(List.class);
Полный пример здесь и соответствующий проход сборки CI и тестовый запуск здесь .
Наша команда уже некоторое время использует это в наших модульных тестах, и это выглядит как самое простое решение для нас.
Для более ранней версии junit вы можете сделать
Class<Map<String, String>> mapClass = (Class) Map.class;
ArgumentCaptor<Map<String, String>> mapCaptor = ArgumentCaptor.forClass(mapClass);
У меня была та же проблема с тестированием в моем приложении для Android. Я использовал ActivityInstrumentationTestCase2
и MockitoAnnotations.initMocks(this);
не работал. Я решил эту проблему с другим классом с соответствующим полем. Например:
class CaptorHolder {
@Captor
ArgumentCaptor<Callback<AuthResponse>> captor;
public CaptorHolder() {
MockitoAnnotations.initMocks(this);
}
}
Затем в методе проверки активности:
HubstaffService hubstaffService = mock(HubstaffService.class);
fragment.setHubstaffService(hubstaffService);
CaptorHolder captorHolder = new CaptorHolder();
ArgumentCaptor<Callback<AuthResponse>> captor = captorHolder.captor;
onView(withId(R.id.signInBtn))
.perform(click());
verify(hubstaffService).authorize(anyString(), anyString(), captor.capture());
Callback<AuthResponse> callback = captor.getValue();
В GitHub Mockito есть открытая проблема именно об этой проблеме.
Я нашел простой обходной путь, который не заставляет вас использовать аннотации в ваших тестах:
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.MockitoAnnotations;
public final class MockitoCaptorExtensions {
public static <T> ArgumentCaptor<T> captorFor(final CaptorTypeReference<T> argumentTypeReference) {
return new CaptorContainer<T>().captor;
}
public static <T> ArgumentCaptor<T> captorFor(final Class<T> argumentClass) {
return ArgumentCaptor.forClass(argumentClass);
}
public interface CaptorTypeReference<T> {
static <T> CaptorTypeReference<T> genericType() {
return new CaptorTypeReference<T>() {
};
}
default T nullOfGenericType() {
return null;
}
}
private static final class CaptorContainer<T> {
@Captor
private ArgumentCaptor<T> captor;
private CaptorContainer() {
MockitoAnnotations.initMocks(this);
}
}
}
То , что происходит здесь в том , что мы создаем новый класс с в @Captor
аннотации и ввести в нее захватчик. Затем мы просто извлекаем захватчик и возвращаем его из нашего статического метода.
В своем тесте вы можете использовать его так:
ArgumentCaptor<Supplier<Set<List<Object>>>> fancyCaptor = captorFor(genericType());
Или с синтаксисом, который похож на Джексона TypeReference
:
ArgumentCaptor<Supplier<Set<List<Object>>>> fancyCaptor = captorFor(
new CaptorTypeReference<Supplier<Set<List<Object>>>>() {
}
);
Это работает, потому что Mockito на самом деле не нуждается в информации о типах (в отличие от сериализаторов, например).
ArrayList
). Вы всегда можете использоватьList
интерфейс, и если вы хотите представить факт, что он ковариантен, то вы можете использоватьextends
:ArgumentCaptor<? extends List<SomeType>>