Beispiel für Mockitos argumentCaptor

141

Kann mir bitte jemand ein Beispiel geben, das zeigt, wie die org.mockito.ArgumentCaptorKlasse verwendet wird und wie sie sich von einfachen Matchern unterscheidet , die mit Mockito versehen sind.

Ich habe die bereitgestellten Mockito-Dokumente gelesen, aber diese veranschaulichen es nicht klar, keiner von ihnen kann es klar erklären.

Ujjwal
quelle

Antworten:

187

Ich stimme dem zu, was @fge gesagt hat. Schauen wir uns das Beispiel an. Stellen Sie sich vor, Sie haben eine Methode:

class A {
    public void foo(OtherClass other) {
        SomeData data = new SomeData("Some inner data");
        other.doSomething(data);
    }
}

Wenn Sie nun die inneren Daten überprüfen möchten, können Sie den Captor verwenden:

// Create a mock of the OtherClass
OtherClass other = mock(OtherClass.class);

// Run the foo method with the mock
new A().foo(other);

// Capture the argument of the doSomething function
ArgumentCaptor<SomeData> captor = ArgumentCaptor.forClass(SomeData.class);
verify(other, times(1)).doSomething(captor.capture());

// Assert the argument
SomeData actual = captor.getValue();
assertEquals("Some inner data", actual.innerData);
Slava Shpitalny
quelle
Wenn doSomething(data)mutiert innerData, ist diese Änderung dann vorhanden assertEquals("Some inner data", actual.innerData)oder wird innerDataso erfasst, wie sie ist, bevor sie doSomething ausgeführt wird?
Cory Klein
@CoryKlein Das OtherClassist ein Mock, und da es jetzt definiert ist, wird der doSomething()Wille eigentlich nichts tun, er zeichnet einfach das übergebene Objekt auf. Dies bedeutet, dass es so wie es ist erfasst wird, bevor doSomethinges ausgeführt wird.
Slava Shpitalny
3
In verify, times(1)ist die Standardeinstellung und kann weggelassen werden.
Inego
Woher weiß ArgumentCaptor, dass foo (other) passiert ist, da es erst nach dem Aufruf von foo (other) instanziiert wird?
AvramPop
1
@AvramPop derjenige, der dies weiß, ist das Scheinobjekt. Es enthält viele Informationen über das Modell. In all diesen Informationen ist auch der Aufrufverlauf für jede Methode mit ihren Parametern enthalten. Wenn Sie die verifyMethode aufrufen , werden diese Informationen verwendet, um Übereinstimmungen mit der von Ihnen durchgeführten Überprüfung auszuführen. Für jeden Parameter wird gefragt, ob er mit dem von ihm überprüften Aufruf übereinstimmt. Wenn ArgumentCaptor aktiviert ist, werden einfach die Werte gespeichert, mit denen es aufgerufen wurde. Wenn es verifyendet, enthält es alle relevanten Aufrufe. Es ist ungefähr so, wie es funktioniert. Hoffe es hilft
Slava Shpitalny
35

Die zwei Hauptunterschiede sind:

  • Wenn Sie nur ein einziges Argument erfassen, können Sie dieses Argument viel ausführlicher und mit offensichtlicherem Code testen.
  • ein ArgumentCaptorkann mehr als einmal erfassen .

Um letzteres zu veranschaulichen, sagen Sie, Sie haben:

final ArgumentCaptor<Foo> captor = ArgumentCaptor.forClass(Foo.class);

verify(x, times(4)).someMethod(captor.capture()); // for instance

Dann kann der Captor Ihnen Zugriff auf alle 4 Argumente gewähren, für die Sie dann Zusicherungen separat ausführen können.

Dies oder eine beliebige Anzahl von Argumenten in der Tat, da a VerificationModenicht auf eine feste Anzahl von Aufrufen beschränkt ist; In jedem Fall erhalten Sie vom Entführer Zugriff auf alle, wenn Sie dies wünschen.

Dies hat auch den Vorteil, dass solche Tests (imho) viel einfacher zu schreiben sind als die Implementierung eigener Tests ArgumentMatcher- insbesondere, wenn Sie Mockito mit Assertj kombinieren.

Oh, und bitte erwägen Sie die Verwendung von TestNG anstelle von JUnit.

fge
quelle
1
Was ist, wenn mehrere Parameter an die Methode übergeben werden - alle unterschiedlichen Typen? Wie können Sie beispielsweise überprüfen, ob der boolesche Parameter wahr ist ?
IgorGanapolsky
21
Können Sie eine Erklärung für Ihren Kommentar liefern: Oh, und bitte erwägen Sie, TestNG anstelle von JUnit zu verwenden. . Warum darüber nachdenken? Warum ändern?
Navigatron
1
@IgorGanapolsky Sie fügen einfach einen weiteren ArgumentCaptor hinzu. ArgumentCaptor <BigDecimal> arg = ArgumentCaptor.forClass (BigDecimal.class); ArgumentCaptor <String> arg2 = ArgumentCaptor.forClass (String.class); Michael Michael = neuer Michael (); michael.sayHi (j); verify (j) .saySomething (arg.capture (), arg2.capture ()); System.out.println ("Wert ist" + arg.getValue ()); System.out.println ("string is" + arg2.getValue ());
Johnwick0831
12

Die Schritte, um eine vollständige Überprüfung durchzuführen, sind:

Bereiten Sie den Captor vor:

ArgumentCaptor<SomeArgumentClass> someArgumentCaptor = ArgumentCaptor.forClass(SomeArgumentClass.class);

Überprüfen Sie, ob der Aufruf abhängig von der Komponente (Mitarbeiter des zu prüfenden Subjekts) mal (1) ist. Dies ist der Standardwert. Sie müssen ihn also hinzufügen.

verify(dependentOnComponent, times(1)).send(someArgumentCaptor.capture());

Lassen Sie das Argument an den Mitarbeiter übergeben

SomeArgumentClass someArgument = messageCaptor.getValue();

someArgument kann für Zusicherungen verwendet werden

Lho Ben
quelle
-2

Hier gebe ich Ihnen ein geeignetes Beispiel für eine Rückrufmethode. Nehmen wir also an, wir haben eine Methode wie method login ():

 public void login() {
    loginService = new LoginService();
    loginService.login(loginProvider, new LoginListener() {
        @Override
        public void onLoginSuccess() {
            loginService.getresult(true);
        }

        @Override
        public void onLoginFaliure() {
            loginService.getresult(false);

        }
    });
    System.out.print("@@##### get called");
}

Ich habe auch die gesamte Hilfsklasse hier eingefügt, um das Beispiel klarer zu machen: loginService-Klasse

public class LoginService implements Login.getresult{
public void login(LoginProvider loginProvider,LoginListener callback){

    String username  = loginProvider.getUsername();
    String pwd  = loginProvider.getPassword();
    if(username != null && pwd != null){
        callback.onLoginSuccess();
    }else{
        callback.onLoginFaliure();
    }

}

@Override
public void getresult(boolean value) {
    System.out.print("login success"+value);
}}

und wir haben Listener LoginListener als:

interface LoginListener {
void onLoginSuccess();

void onLoginFaliure();

}}

Jetzt wollte ich nur die Methode login () der Klasse Login testen

 @Test
public void loginTest() throws Exception {
    LoginService service = mock(LoginService.class);
    LoginProvider provider = mock(LoginProvider.class);
    whenNew(LoginProvider.class).withNoArguments().thenReturn(provider);
    whenNew(LoginService.class).withNoArguments().thenReturn(service);
    when(provider.getPassword()).thenReturn("pwd");
    when(provider.getUsername()).thenReturn("username");
    login.getLoginDetail("username","password");

    verify(provider).setPassword("password");
    verify(provider).setUsername("username");

    verify(service).login(eq(provider),captor.capture());

    LoginListener listener = captor.getValue();

    listener.onLoginSuccess();

    verify(service).getresult(true);

Vergessen Sie auch nicht, Anmerkungen über der Testklasse als hinzuzufügen

@RunWith(PowerMockRunner.class)
@PrepareForTest(Login.class)
Vikram singh
quelle
1
Sollte es sich nicht auf ArgumentCaptor beziehen?
Felipe Martins Melo
Ja, wir erfassen den Listener, der an die Methode login () übergeben wurde, im Beispiel login (LoginProvider loginProvider, LoginListener-Rückruf)
Vikram singh
Wo ist das captorin deiner Antwort definiert?
tom_mai78101
ArgumentCaptor <LoginListener> listenerCaptor = ArgumentCaptor.forClass (LoginListener.class);
Vikram singh