So erfassen Sie mit mockito eine Liste eines bestimmten Typs

301

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);
Andreas Köberle
quelle
8
Ich finde es eine schreckliche Idee, hier eine konkrete Listenimplementierung zu verwenden ( ArrayList). Sie können immer ListSchnittstelle verwenden, und wenn Sie die Tatsache darstellen möchten, dass es kovariant ist, dann können Sie verwenden extends:ArgumentCaptor<? extends List<SomeType>>
Tenshi

Antworten:

533

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()));
    }
}
Crunchdog
quelle
70
Ich bevorzuge die Verwendung MockitoAnnotations.initMocks(this)in der @BeforeMethode, 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.
John B
4
Ich bin mir nicht sicher, ob dieses Beispiel vollständig ist. Ich bekomme ... Fehler: (240, 40) Java: Variabler Captor wurde möglicherweise nicht initialisiert. Ich mag Tenshis Antwort unten
Michael Dausmann,
1
Ich bin auf dasselbe Problem gestoßen und habe diesen Blog-Beitrag gefunden, der mir ein bisschen geholfen hat: blog.jdriven.com/2012/10/… . Es enthält einen Schritt zur Verwendung von MockitoAnnotations.initMocks, nachdem Sie die Anmerkung in Ihre Klasse eingefügt haben. Eine Sache, die mir aufgefallen ist, ist, dass Sie es nicht in einer lokalen Variablen haben können.
SlopeOak
1
@ chamzz.dot ArgumentCaptor <ArrayList <SomeType>> captor; erfasst bereits ein Array von "SomeType" <- das ist ein bestimmter Typ, nicht wahr?
Miguel R. Santaella
1
Normalerweise bevorzuge ich List anstelle von ArrayList in der Captor-Deklaration: ArgumentCaptor <List <SomeType>> captor;
Miguel R. Santaella
146

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 @CaptorAnmerkung. Es kann immer noch nicht zwischen einem ArrayList<SomeType>und einem unterschieden werden ArrayList<OtherType>.)

Bearbeiten:

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);
Paŭlo Ebermann
quelle
49
Das Beispiel, das Sie gezeigt haben, kann vereinfacht werden, basierend auf der Tatsache, dass Java Typinferenz für die statischen Methodenaufrufe macht:ArgumentCaptor<List<SimeType>> argument = ArgumentCaptor.forClass((Class) List.class);
Tenshi
4
Verwenden Sie die Anmerkung über der Definitionszeile des Argument-Captors , um die Warnung für nicht aktivierte oder unsichere Vorgänge zu deaktivieren @SuppressWarnings("unchecked"). Außerdem ist das Casting in Classüberflüssig.
Herr
1
Das Casting zu Classist in meinen Tests nicht überflüssig.
Wim Deblauwe
16

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.
Rogerdpack
quelle
2
Sie können vor der Deklaration @SuppressWarnings ("rawtypes") hinzufügen, um Warnungen zu deaktivieren.
pkalinow
9
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"));
kkmike999
quelle
4

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.

mrts
quelle
2

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);
quzhi65222714
quelle
1

Ich hatte das gleiche Problem mit der Testaktivität in meiner Android-App. Ich habe benutzt ActivityInstrumentationTestCase2und 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();
Timofey Orischenko
quelle
0

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 @CaptorAnnotation 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).

Jezor
quelle