wake-up-neo.com

Mockito: Wie wurde überprüft, ob die Methode für ein Objekt aufgerufen wurde, das innerhalb einer Methode erstellt wurde?

Ich bin neu bei Mockito.

Wie kann ich mit Mockito überprüfen, ob someMethod genau einmal aufgerufen wurde, nachdem foo aufgerufen wurde?

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

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

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

dabei ist bar eine Scheininstanz von Bar.

238
mre

Abhängigkeitsspritze

Wenn Sie die Bar-Instanz oder eine Fabrik einfügen, in der die Bar-Instanz erstellt wird (oder eine der anderen 483 Möglichkeiten, dies durchzuführen), haben Sie den erforderlichen Zugriff, um den Test durchzuführen.

Fabrik Beispiel:

Gegeben eine Foo-Klasse wie folgt geschrieben: 

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 folgendermaßen 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, wie TDD Ihren Code gestalten kann.

273
csturtz

Die klassische Antwort lautet: "Sie tun es nicht." Sie testen die öffentliche API von Foo, nicht deren Interna.

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

18

Wenn Sie keine DI oder Factories 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 wird die Klasse, die Ihre foo-Methode aufruft, folgendermaßen vorgehen:

public class thirdClass {

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

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.

Der Nachteil ist natürlich, dass Sie dem Anrufer die Einstellung des Bar-Objekts gestatten. 

Ich hoffe es hilft.

11
raspacorp

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

  • mockito-all 1.10.8
  • powermock-Kern 1.6.1
  • powermock-module-junit4 1.6.1
  • powermock-api-mockito 1.6.1
  • juni 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 Output

8
javaPlease42

Ich denke, Mockito @InjectMocks ist der Weg zu gehen. 

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

  1. Konstruktorinjektion
  2. Eigenschaftssetzer-Injektion
  3. Feldinjektion

Weitere Infos in docs

Nachfolgend 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();
    }
}
7
siulkilulki

Ja, wenn Sie 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 aus dem Aufruf an den Konstruktor zurückgegeben wird. Dann überprüfen Sie den Spott. Das heißt, Csturtz ist die "richtige" Antwort.

Hier ist der Link zu Scheinkonstruktion neuer Objekte

3
John B

Eine andere einfache Möglichkeit wäre, dem bar.someMethod () eine Protokollanweisung hinzuzufügen und dann festzustellen, dass Sie die besagte Nachricht sehen können, wenn Ihr Test ausgeführt wurde. Siehe Beispiele hier: Wie macht man eine JUnit-Bestätigung für eine Nachricht in einem Logger

Das ist besonders praktisch, wenn Ihre Bar.someMethod () private ist.

0
Nestor Milyaev