Wie man mit Mockito leere Methoden verspottet

938

Wie verspotte ich Methoden mit dem Rückgabetyp void?

Ich habe ein Beobachtermuster implementiert, kann es aber mit Mockito nicht verspotten, weil ich nicht weiß wie.

Und ich habe versucht, ein Beispiel im Internet zu finden, aber es ist mir nicht gelungen.

Meine Klasse sieht so aus:

public class World {

    List<Listener> listeners;

    void addListener(Listener item) {
        listeners.add(item);
    }

    void doAction(Action goal,Object obj) {
        setState("i received");
        goal.doAction(obj);
        setState("i finished");
    }

    private string state;
    //setter getter state
} 

public class WorldTest implements Listener {

    @Test public void word{
    World  w= mock(World.class);
    w.addListener(this);
    ...
    ...

    }
}

interface Listener {
    void doAction();
}

Das System wird nicht mit Mock ausgelöst.

Ich möchte den oben genannten Systemstatus anzeigen. Und machen Sie Aussagen nach ihnen.

ibrahimyilmaz
quelle
6
Beachten Sie, dass void-Methoden auf Mocks standardmäßig nichts bewirken!
Linie
1
@Line, das habe ich gesucht. Es scheint offensichtlich, nachdem Sie es gesagt haben. Es wird jedoch ein Verspottungsprinzip hervorgehoben: Sie müssen nur Methoden verspotteter Klassen wegen ihrer Auswirkungen verspotten, z. B. einen Rückgabewert oder eine Ausnahme. Vielen Dank!
Allenjom

Antworten:

1145

Schauen Sie sich die Mockito API-Dokumente an . Als das verknüpfte Dokument (Point # 12) erwähnt Sie eine der verwenden können doThrow(), doAnswer(), doNothing(),doReturn() Familie von Methoden aus Mockito Rahmen Leere Methoden zu verspotten.

Zum Beispiel,

Mockito.doThrow(new Exception()).when(instance).methodName();

oder wenn Sie es mit Follow-up-Verhalten kombinieren möchten,

Mockito.doThrow(new Exception()).doNothing().when(instance).methodName();

Angenommen, Sie möchten den Setter setState(String s)in der unten stehenden Klasse World doAnswerverspotten, verwendet der Code die Methode, um den zu verspotten setState.

World  mockWorld = mock(World.class); 
doAnswer(new Answer<Void>() {
    public Void answer(InvocationOnMock invocation) {
      Object[] args = invocation.getArguments();
      System.out.println("called with arguments: " + Arrays.toString(args));
      return null;
    }
}).when(mockWorld).setState(anyString());
Sateesh
quelle
8
@qualidafial: Ja, ich denke, die Parametrisierung auf Void wäre besser, da sie besser vermittelt, dass ich nicht an dem Rückgabetyp interessiert bin. Ich war mir dieses Konstrukts nicht bewusst, danke, dass Sie darauf hingewiesen haben.
Sateesh
2
doThrow ist jetzt # 5 (auch für mich mit doThrow wurde die Meldung "'void' Typ hier nicht erlaubt" für Follower
behoben
@qualidafial: Ich denke, der Rückgabetyp des Answer.answer-Aufrufs entspricht nicht der ursprünglichen Methode, sondern dem doAnswer-Aufruf, vermutlich, wenn Sie in Ihrem Test etwas anderes mit diesem Wert tun möchten.
12.17
1
:( beim Versuch, Version 16.0.1 von RateLimiter.java in guava doNothing () zu verspotten, wenn (mockLimiterReject) .setRate (100) zum Aufrufen der setRate des RateLimiter führt, was zu einem Nullzeiger führt, da der Mutex aus irgendeinem Grund null ist, sobald mockito bytecodiert ist es also verspottete es nicht meine setRate Methode :( sondern nannte es stattdessen :(
Dean Hiller
2
@ DeanHiller bemerken das setRate()ist finalund kann daher nicht verspottet werden. Versuchen Sie stattdessen create()eine Instanz, die das tut, was Sie brauchen. Es sollte nicht nötig sein, sich zu verspotten RateLimiter.
dimo414
113

Ich glaube, ich habe eine einfachere Antwort auf diese Frage gefunden. Um die reale Methode für nur eine Methode aufzurufen (auch wenn sie eine ungültige Rückgabe hat), können Sie dies tun:

Mockito.doCallRealMethod().when(<objectInstance>).<method>();
<objectInstance>.<method>();

Oder Sie können die reale Methode für alle Methoden dieser Klasse aufrufen, indem Sie Folgendes tun:

<Object> <objectInstance> = mock(<Object>.class, Mockito.CALLS_REAL_METHODS);
MarcioB
quelle
13
Dies ist die wahre Antwort hier. Die spy () -Methode funktioniert einwandfrei, ist jedoch im Allgemeinen reserviert, wenn das Objekt fast alles normal ausführen soll.
Biggusjimmus
1
Was bedeutet das? Rufen Sie tatsächlich die Methoden auf? Ich habe Mockito noch nie benutzt.
obesechicken13
Ja, der Mock ruft die realen Methoden auf. Wenn Sie @Mock verwenden, können Sie dasselbe angeben mit: @Mock (answer = Answers.CALLS_REAL_METHODS), um dieselben Ergebnisse zu erhalten.
Ale
70

Wenn Sie zusätzlich zu dem, was @sateesh gesagt hat, eine void-Methode verspotten möchten, um zu verhindern, dass der Test sie aufruft, können Sie Folgendes verwenden Spy:

World world = new World();
World spy = Mockito.spy(world);
Mockito.doNothing().when(spy).methodToMock();

Wenn Sie Ihren Test ausführen möchten, stellen Sie sicher, dass Sie die zu testende Methode für das spyObjekt und nicht für das worldObjekt aufrufen . Zum Beispiel:

assertEquals(0,spy.methodToTestThatShouldReturnZero());
Omri374
quelle
57

Die Lösung des sogenannten Problems besteht darin, eine spy Mockito.spy (...) anstelle einer mock Mockito.mock (..) zu verwenden .

Spion ermöglicht es uns, teilweise zu verspotten. Mockito ist gut in dieser Angelegenheit. Da Sie eine Klasse haben, die nicht vollständig ist, verspotten Sie auf diese Weise einen erforderlichen Platz in dieser Klasse.

ibrahimyilmaz
quelle
3
Ich bin hier reingestolpert, weil ich ein ähnliches Problem hatte (zufällig habe ich auch eine Subjekt / Beobachter-Interaktion getestet). Ich benutze bereits einen Spion, möchte aber, dass die 'SubjectChanged'-Methode etwas anderes macht. Ich könnte `verify (Beobachter) .subjectChanged (Betreff) verwenden, um zu sehen, dass die Methode aufgerufen wurde. Aber aus irgendeinem Grund würde ich die Methode lieber überschreiben. Dafür war eine Kombination aus Sateeshs Ansatz und Ihrer Antwort hier der richtige Weg ...
gMale
32
Nein, dies hilft nicht wirklich dabei, leere Methoden zu verspotten. Der Trick besteht darin, eine der vier statischen Mockito-Methoden zu verwenden, die in der Antwort von Sateesh aufgeführt sind.
Dawood ibn Kareem
2
@Gurnard für Ihre Frage werfen Sie einen Blick auf diese stackoverflow.com/questions/1087339/… .
ibrahimyilmaz
Diese Dosierung funktioniert tatsächlich.
Nilotpal
34

Zuallererst: Sie sollten immer mockito static importieren, damit der Code viel besser lesbar (und intuitiver) ist:

import static org.mockito.Mockito.*;

Zum teilweisen Verspotten und Beibehalten der ursprünglichen Funktionalität im Rest bietet mockito "Spy" an.

Sie können es wie folgt verwenden:

private World world = spy(World.class);

Um zu verhindern, dass eine Methode ausgeführt wird, können Sie Folgendes verwenden:

doNothing().when(someObject).someMethod(anyObject());

Um einer Methode ein benutzerdefiniertes Verhalten zu verleihen, verwenden Sie "when" mit einem "thenReturn":

doReturn("something").when(this.world).someMethod(anyObject());

Weitere Beispiele finden Sie in den hervorragenden Mockito-Beispielen im Dokument.

fl0w
quelle
4
Was hat statischer Import damit zu tun, dass er besser lesbar ist?
Jsonbourne
2
buchstäblich nichts.
Spezialisiert
2
Ich denke, es ist Geschmackssache, ich möchte nur, dass die Aussage (fast) wie ein englischer Satz aussieht und der Class.methodname (). Something () vs. methodname (). Etwas ist weniger flüssig lesbar.
fl0w
1
Wenn Sie in einem Team arbeiten, ist es für jeden anderen ein Problem, zu sehen, woher die Methode kommt, da der Import häufig einen Platzhalter enthält.
LowKeyEnergy
Das ist richtig, aber für Testcode und Mockito sollte dies offensichtlich sein und kein Problem darstellen.
fl0w
27

Wie man leere Methoden mit mockito verspottet - es gibt zwei Möglichkeiten:

  1. doAnswer - Wenn wir wollen, dass unsere verspottete Void-Methode etwas bewirkt (verspotten Sie das Verhalten, obwohl es nichtig ist).
  2. doThrow- Dann gibt es, Mockito.doThrow()wenn Sie eine Ausnahme von der verspotteten void-Methode auslösen möchten.

Im Folgenden finden Sie ein Beispiel für die Verwendung (kein idealer Anwendungsfall, sondern nur zur Veranschaulichung der grundlegenden Verwendung).

@Test
public void testUpdate() {

    doAnswer(new Answer<Void>() {

        @Override
        public Void answer(InvocationOnMock invocation) throws Throwable {
            Object[] arguments = invocation.getArguments();
            if (arguments != null && arguments.length > 1 && arguments[0] != null && arguments[1] != null) {

                Customer customer = (Customer) arguments[0];
                String email = (String) arguments[1];
                customer.setEmail(email);

            }
            return null;
        }
    }).when(daoMock).updateEmail(any(Customer.class), any(String.class));

    // calling the method under test
    Customer customer = service.changeEmail("[email protected]", "[email protected]");

    //some asserts
    assertThat(customer, is(notNullValue()));
    assertThat(customer.getEmail(), is(equalTo("[email protected]")));

}

@Test(expected = RuntimeException.class)
public void testUpdate_throwsException() {

    doThrow(RuntimeException.class).when(daoMock).updateEmail(any(Customer.class), any(String.class));

    // calling the method under test
    Customer customer = service.changeEmail("[email protected]", "[email protected]");

}
}

Weitere Informationen zum Verspotten und Testen von Void- Methoden mit Mockito finden Sie in meinem Beitrag Wie man sich mit Mockito verspottet (Eine umfassende Anleitung mit Beispielen)

Dilini Rajapaksha
quelle
1
Tolles Beispiel. Hinweis: In Java 8 ist es möglicherweise etwas besser, ein Lambda anstelle einer anonymen Klasse zu verwenden: 'doAnswer ((Answer <Void>) Aufruf -> {// CODE}). When (mockInstance) .add (Methode ()); '
Miwe
16

Hinzufügen einer weiteren Antwort zum Haufen (kein Wortspiel beabsichtigt) ...

Sie müssen die doAnswer-Methode aufrufen, wenn Sie keine Spys verwenden können. Sie müssen jedoch nicht unbedingt Ihre eigene Antwort rollen . Es gibt mehrere Standardimplementierungen. Insbesondere CallsRealMethods .

In der Praxis sieht es ungefähr so ​​aus:

doAnswer(new CallsRealMethods()).when(mock)
        .voidMethod(any(SomeParamClass.class));

Oder:

doAnswer(Answers.CALLS_REAL_METHODS.get()).when(mock)
        .voidMethod(any(SomeParamClass.class));
javamonkey79
quelle
16

In Java 8 kann dies etwas sauberer gemacht werden, vorausgesetzt, Sie haben einen statischen Import für org.mockito.Mockito.doAnswer:

doAnswer(i -> {
  // Do stuff with i.getArguments() here
  return null;
}).when(*mock*).*method*(*methodArguments*);

Das return null;ist wichtig und ohne es schlägt die Kompilierung mit einigen ziemlich undurchsichtigen Fehlern fehl, da es nicht in der Lage ist, eine geeignete Überschreibung für zu findendoAnswer .

Zum Beispiel könnte ein ExecutorService, der alle Runnableübergebenen Daten sofort ausführt execute(), implementiert werden mit:

doAnswer(i -> {
  ((Runnable) i.getArguments()[0]).run();
  return null;
}).when(executor).execute(any());
Tim B.
quelle
2
In einer Zeile: Mockito.doAnswer ((i) -> null) .when (Instanz) .method (any ());
Akshay Thorve
@AkshayThorve Das funktioniert nicht, wenn du tatsächlich Sachen mit i machen willst.
Tim B
7

Ich denke, Ihre Probleme sind auf Ihre Teststruktur zurückzuführen. Ich fand es schwierig, Spott mit der traditionellen Methode zur Implementierung von Schnittstellen in der Testklasse zu mischen (wie Sie es hier getan haben).

Wenn Sie den Listener als Mock implementieren, können Sie die Interaktion überprüfen.

Listener listener = mock(Listener.class);
w.addListener(listener);
world.doAction(..);
verify(listener).doAction();

Dies sollte Sie davon überzeugen, dass die 'Welt' das Richtige tut.

Ashley
quelle
0

Verwenden von Mockito.doThrow wie in:

Mockito.doThrow(new Exception()).when(instance).methodName();

Sie können dieses schöne Beispiel ausprobieren:

public void testCloseStreamOnException() throws Exception {
    OutputStream outputStream = Mockito.mock(OutputStream.class);
    IFileOutputStream ifos = new IFileOutputStream(outputStream);
    Mockito.doThrow(new IOException("Dummy Exception")).when(outputStream).flush();
    try {
      ifos.close();
      fail("IOException is not thrown");
    } catch (IOException ioe) {
      assertEquals("Dummy Exception", ioe.getMessage());
    }
    Mockito.verify(outputStream).close();
  }

Quelle: http://apisonar.com/java-examples/org.mockito.Mockito.doThrow.html#Example-19

APISonar
quelle