Чи є спосіб захоплення списку конкретного типу за допомогою 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>
, і, таким чином, ви не можете безпечно передати такий об'єкт методу, що вимагає 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>
та an ArrayList<OtherType>
.)
Погляньте також на коментар tenshi . Ви можете змінити оригінальний код з 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"));
На основі коментарів @ tenshi та @ pkalinow (також kudos to @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>>