Gibt es eine Möglichkeit, eine Liste eines bestimmten Typs mit mockitos ArgumentCaptore zu erfassen? Das funktioniert nicht:
ArgumentCaptor<ArrayList<SomeType>> argument = ArgumentCaptor.forClass(ArrayList.class);
Gibt es eine Möglichkeit, eine Liste eines bestimmten Typs mit mockitos ArgumentCaptore zu erfassen? Das funktioniert nicht:
ArgumentCaptor<ArrayList<SomeType>> argument = ArgumentCaptor.forClass(ArrayList.class);
Antworten:
Das verschachtelte Generika-Problem kann mit der Annotation @Captor vermieden werden :
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)
in der @Before
Methode, anstatt einen Läufer zu verwenden, der die Möglichkeit ausschließt, einen anderen Läufer zu verwenden. +1, danke, dass Sie auf die Anmerkung hingewiesen haben.
Ja, dies ist ein allgemeines Generika-Problem, nicht mockito-spezifisch.
Es gibt kein Klassenobjekt für ArrayList<SomeType>
, und daher können Sie ein solches Objekt nicht typsicher an eine Methode übergeben, für die a erforderlich ist Class<ArrayList<SomeType>>
.
Sie können das Objekt in den richtigen Typ umwandeln:
Class<ArrayList<SomeType>> listClass =
(Class<ArrayList<SomeType>>)(Class)ArrayList.class;
ArgumentCaptor<ArrayList<SomeType>> argument = ArgumentCaptor.forClass(listClass);
Dies gibt einige Warnungen vor unsicheren Casts aus, und natürlich kann Ihr ArgumentCaptor nicht wirklich zwischen ArrayList<SomeType>
und unterscheiden, ArrayList<AnotherType>
ohne die Elemente zu überprüfen.
(Wie in der anderen Antwort erwähnt, gibt es zwar ein allgemeines generisches Problem, es gibt jedoch eine Mockito-spezifische Lösung für das Typensicherheitsproblem mit der @Captor
Anmerkung. Es kann immer noch nicht zwischen einem ArrayList<SomeType>
und einem unterschieden werden ArrayList<OtherType>
.)
Schauen Sie sich auch Tenshis Kommentar an. Sie können den Originalcode von Paŭlo Ebermann in diesen ändern (viel einfacher).
final ArgumentCaptor<List<SomeType>> listCaptor
= ArgumentCaptor.forClass((Class) List.class);
ArgumentCaptor<List<SimeType>> argument = ArgumentCaptor.forClass((Class) List.class);
@SuppressWarnings("unchecked")
. Außerdem ist das Casting in Class
überflüssig.
Class
ist in meinen Tests nicht überflüssig.
Wenn Sie keine Angst vor der alten Java-Semantik (nicht typsichere generische Semantik) haben, funktioniert dies auch und ist einigermaßen einfach:
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"));
Basierend auf den Kommentaren von @ tenshi und @ pkalinow (auch ein großes Lob an @rogerdpack) ist das Folgende eine einfache Lösung zum Erstellen eines Listenargument-Captors, der auch die Warnung "Verwendet nicht aktivierte oder unsichere Operationen" deaktiviert :
@SuppressWarnings("unchecked")
final ArgumentCaptor<List<SomeType>> someTypeListArgumentCaptor =
ArgumentCaptor.forClass(List.class);
Vollständiges Beispiel hier und entsprechender CI-Build und Testlauf hier .
Unser Team verwendet dies seit einiger Zeit in unseren Unit-Tests und dies scheint die einfachste Lösung für uns zu sein.
Für eine frühere Version von junit können Sie dies tun
Class<Map<String, String>> mapClass = (Class) Map.class;
ArgumentCaptor<Map<String, String>> mapCaptor = ArgumentCaptor.forClass(mapClass);
Ich hatte das gleiche Problem mit der Testaktivität in meiner Android-App. Ich habe benutzt ActivityInstrumentationTestCase2
und MockitoAnnotations.initMocks(this);
nicht gearbeitet. Ich habe dieses Problem mit einer anderen Klasse mit jeweils Feld gelöst. Zum Beispiel:
class CaptorHolder {
@Captor
ArgumentCaptor<Callback<AuthResponse>> captor;
public CaptorHolder() {
MockitoAnnotations.initMocks(this);
}
}
Dann in der Aktivitätstestmethode:
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();
In Mockitos GitHub gibt es ein offenes Problem mit genau diesem Problem.
Ich habe eine einfache Problemumgehung gefunden, die Sie nicht zwingt, Anmerkungen in Ihren Tests zu verwenden:
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);
}
}
}
Was hier passiert ist, dass wir eine neue Klasse mit der @Captor
Annotation erstellen und den Captor in sie einfügen. Dann extrahieren wir einfach den Captor und geben ihn von unserer statischen Methode zurück.
In Ihrem Test können Sie es so verwenden:
ArgumentCaptor<Supplier<Set<List<Object>>>> fancyCaptor = captorFor(genericType());
Oder mit einer Syntax, die der von Jackson ähnelt TypeReference
:
ArgumentCaptor<Supplier<Set<List<Object>>>> fancyCaptor = captorFor(
new CaptorTypeReference<Supplier<Set<List<Object>>>>() {
}
);
Es funktioniert, weil Mockito eigentlich keine Typinformationen benötigt (im Gegensatz zu Serialisierern zum Beispiel).
ArrayList
). Sie können immerList
Schnittstelle verwenden, und wenn Sie die Tatsache darstellen möchten, dass es kovariant ist, dann können Sie verwendenextends
:ArgumentCaptor<? extends List<SomeType>>