Mockito: Wie kann überprüft werden, ob eine Methode für ein in einer Methode erstelltes Objekt aufgerufen wurde?

322

Ich bin neu in Mockito.

Wie kann ich in der folgenden Klasse mit Mockito überprüfen, ob someMethodgenau einmal aufgerufen wurde, nachdem fooes aufgerufen wurde?

public class Foo
{
    public void foo(){
        Bar bar = new Bar();
        bar.someMethod();
    }
}

Ich möchte den folgenden Bestätigungsaufruf tätigen:

verify(bar, times(1)).someMethod();

Wo barist eine verspottete Instanz von Bar.

mre
quelle
2
stackoverflow.com/questions/6520242/… - Aber ich möchte PowerMock nicht verwenden.
Mehr
Ändern Sie die API oder PowerMock. Einer der Beiden.
John B
Wie kann man so etwas abdecken? public synchronized void start (BundleContext bundleContext) löst eine Ausnahme aus {BundleContext bc = bundleContext; logger.info ("HTTP SERVICE BUNDLE STARTEN"); this.tracker = neuer ServiceTracker (bc, HttpService.class.getName (), null) {@Override public Object addService (ServiceReference serviceRef) {httpService = (HttpService) super.addingService (serviceRef); registerServlets (); return httpService; }}}
ShAkKiR

Antworten:

365

Abhängigkeitsspritze

Wenn Sie die Bar-Instanz oder eine Factory einfügen, die zum Erstellen der Bar-Instanz verwendet wird (oder eine der anderen 483 Möglichkeiten, dies zu tun), haben Sie den erforderlichen Zugriff, um den Test durchzuführen.

Fabrikbeispiel:

Bei einer so geschriebenen Foo-Klasse:

public class Foo {
  private BarFactory barFactory;

  public Foo(BarFactory factory) {
    this.barFactory = factory;
  }

  public void foo() {
    Bar bar = this.barFactory.createBar();
    bar.someMethod();
  }
}

In Ihrer Testmethode können Sie eine BarFactory wie folgt injizieren:

@Test
public void testDoFoo() {
  Bar bar = mock(Bar.class);
  BarFactory myFactory = new BarFactory() {
    public Bar createBar() { return bar;}
  };

  Foo foo = new Foo(myFactory);
  foo.foo();

  verify(bar, times(1)).someMethod();
}

Bonus: Dies ist ein Beispiel dafür, wie TDD das Design Ihres Codes steuern kann.

csturtz
quelle
6
Gibt es eine Möglichkeit, dies zu tun, ohne die Klasse für Unit-Tests zu ändern?
mre
6
Bar bar = mock(Bar.class)anstelle vonBar bar = new Bar();
John B
7
nicht dass ich wüsste. Ich schlage jedoch nicht vor, dass Sie die Klasse nur zum Testen von Einheiten ändern. Dies ist wirklich ein Gespräch über sauberen Code und die SRP. Oder ... liegt es in der Verantwortung der Methode foo () in der Klasse Foo, ein Bar-Objekt zu erstellen. Wenn die Antwort "Ja" lautet, handelt es sich um ein Implementierungsdetail, und Sie sollten sich keine Gedanken darüber machen, die Interaktion speziell zu testen (siehe Antwort von @ Michael). Wenn die Antwort Nein lautet, ändern Sie die Klasse, da Ihre Schwierigkeit beim Testen eine rote Fahne ist, dass Ihr Design ein wenig verbessert werden muss (daher der Bonus, den ich hinzugefügt habe, wie TDD das Design antreibt).
Csturtz
3
Können Sie ein "echtes" Objekt an Mockitos "Verifizieren" übergeben?
John B
4
Sie können auch die Fabrik verspotten: BarFactory myFactory = mock(BarFactory.class); when(myFactory.createBar()).thenReturn(bar);
Levsa
18

Die klassische Antwort lautet: "Das tust du nicht." Sie testen die öffentliche API von Foo, nicht deren Interna.

Gibt es ein Verhalten des FooObjekts (oder, weniger gut, eines anderen Objekts in der Umgebung), das davon betroffen ist foo()? Wenn ja, testen Sie das. Und wenn nicht, was macht die Methode?

Michael Brewer-Davis
quelle
4
Was würden Sie hier eigentlich testen? Die öffentliche API von Fooist public void foo(), wo die Interna nur barbezogen sind.
siehe
15
Das Testen nur der öffentlichen API ist in Ordnung, bis es echte Fehler mit Nebenwirkungen gibt, die getestet werden müssen. Zum Beispiel Überprüfung , dass eine private Methode seiner HTTP - Verbindungen ordnungsgemäß endet ist übertrieben , bis Sie , dass die private Methode zu entdecken ist nicht seine Verbindungen richtig schließen, und somit ist ein massives Problem verursacht. An diesem Punkt, Mockito und verify()werde in der Tat sehr hilfreich, auch wenn du nicht mehr am heiligen Altar der Integrationstests verehrst.
Dawngerpony
@DuffJ Ich verwende kein Java, aber das klingt nach etwas, das Ihr Compiler oder Code-Analyse-Tool erkennen sollte.
user247702
3
Ich stimme DuffJ zu, während funktionale Programmierung Spaß macht, kommt ein Punkt, an dem Ihr Code mit der Außenwelt interagiert. Egal, ob Sie es "Interna", "Nebenwirkungen" oder "Funktionalität" nennen, Sie möchten diese Interaktion auf jeden Fall testen: Wenn es passiert und wenn es richtig oft und mit den richtigen Argumenten passiert. @Stijn: Es könnte ein schlechtes Beispiel gewesen sein (aber wenn mehrere Verbindungen geöffnet und nur einige geschlossen werden sollten, wird es interessant). Ein besseres Beispiel wäre, zu überprüfen, ob die richtigen Daten über die Verbindung gesendet wurden.
Andras Balázs Lajtha
13

Wenn Sie DI oder Fabriken nicht verwenden möchten. Sie können Ihre Klasse auf etwas knifflige Weise umgestalten:

public class Foo {
    private Bar bar;

    public void foo(Bar bar){
        this.bar = (bar != null) ? bar : new Bar();
        bar.someMethod();
        this.bar = null;  // for simulating local scope
    }
}

Und deine Testklasse:

@RunWith(MockitoJUnitRunner.class)
public class FooTest {
    @Mock Bar barMock;
    Foo foo;

    @Test
    public void testFoo() {
       foo = new Foo();
       foo.foo(barMock);
       verify(barMock, times(1)).someMethod();
    }
}

Dann macht die Klasse, die Ihre foo-Methode aufruft, Folgendes:

public class thirdClass {

   public void someOtherMethod() {
      Foo myFoo = new Foo();
      myFoo.foo(null);
   }
}

Wie Sie sehen können, wenn Sie die Methode auf diese Weise aufrufen, müssen Sie die Bar-Klasse nicht in eine andere Klasse importieren, die Ihre foo-Methode aufruft, was möglicherweise etwas ist, das Sie möchten.

Der Nachteil ist natürlich, dass Sie dem Anrufer erlauben, das Balkenobjekt festzulegen.

Ich hoffe es hilft.

raspacorp
quelle
3
Ich denke, das ist ein Anti-Muster. Abhängigkeiten sollten injiziert werden, Punkt. Wenn Sie eine optional injizierte Abhängigkeit nur zum Testen zulassen, wird absichtlich vermieden, dass der Code verbessert wird, und es wird absichtlich etwas anderes als der Code getestet, der in der Produktion ausgeführt wird. Beides sind schreckliche, schreckliche Dinge, die zu tun sind.
ErikE
8

Lösung für Ihren Beispielcode mit PowerMockito.whenNew

  • mockito-all 1.10.8
  • Powermock-Core 1.6.1
  • powermock-module-junit4 1.6.1
  • powermock-api-mockito 1.6.1
  • junit 4.12

FooTest.java

package foo;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

//Both @PrepareForTest and @RunWith are needed for `whenNew` to work 
@RunWith(PowerMockRunner.class)
@PrepareForTest({ Foo.class })
public class FooTest {

    // Class Under Test
    Foo cut;

    @Mock
    Bar barMock;

    @Before
    public void setUp() throws Exception {
        cut = new Foo();

    }

    @After
    public void tearDown() {
        cut = null;

    }

    @Test
    public void testFoo() throws Exception {

        // Setup
        PowerMockito.whenNew(Bar.class).withNoArguments()
                .thenReturn(this.barMock);

        // Test
        cut.foo();

        // Validations
        Mockito.verify(this.barMock, Mockito.times(1)).someMethod();

    }

}

JUnit-Ausgabe JUnit-Ausgabe

javaPlease42
quelle
8

Ich denke Mockito @InjectMocks ist der Weg.

Abhängig von Ihrer Absicht können Sie Folgendes verwenden:

  1. Konstruktorinjektion
  2. Property Setter Injection
  3. Feldinjektion

Weitere Infos in den Dokumenten

Unten ist ein Beispiel mit Feldinjektion:

Klassen:

public class Foo
{
    private Bar bar = new Bar();

    public void foo() 
    {
        bar.someMethod();
    }
}

public class Bar
{
    public void someMethod()
    {
         //something
    }
}

Prüfung:

@RunWith(MockitoJUnitRunner.class)
public class FooTest
{
    @Mock
    Bar bar;

    @InjectMocks
    Foo foo;

    @Test
    public void FooTest()
    {
        doNothing().when( bar ).someMethod();
        foo.foo();
        verify(bar, times(1)).someMethod();
    }
}
siulkilulki
quelle
3

Ja, wenn Sie es wirklich wollen / müssen, können Sie PowerMock verwenden. Dies sollte als letzter Ausweg betrachtet werden. Mit PowerMock können Sie veranlassen, dass ein Aufruf vom Aufruf an den Konstruktor zurückgegeben wird. Führen Sie dann die Überprüfung auf dem Mock durch. Das heißt, csturtz ist die "richtige" Antwort.

Hier ist der Link zur Scheinkonstruktion neuer Objekte

John B.
quelle