Mockito: jak sprawdzić, czy metoda została wywołana na obiekcie utworzonym w ramach metody?

322

Jestem nowy w Mockito.

Biorąc pod uwagę poniższą klasę, jak mogę użyć Mockito, aby sprawdzić, czy wywołano someMethoddokładnie raz po foowywołaniu?

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

Chciałbym wykonać następujące połączenie weryfikacyjne,

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

gdzie barjest kpina z Bar.

mre
źródło
2
stackoverflow.com/questions/6520242/… - Ale nie chcę używać PowerMock.
pan
Zmień API lub PowerMock. Jeden z dwóch.
John B
Jak pokryć coś takiego? publiczny zsynchronizowany nieważny start (BundleContext bundleContext) zgłasza wyjątek {BundleContext bc = bundleContext; logger.info („URUCHAMIANIE ZESTAWU USŁUG HTTP”); this.tracker = new ServiceTracker (bc, HttpService.class.getName (), null) {@Override public Object addedService (ServiceReference serviceRef) {httpService = (HttpService) super.addingService (serviceRef); registerServlets (); zwraca httpService; }}}
ShAkKiR

Odpowiedzi:

365

Wstrzykiwanie zależności

Jeśli wstrzykujesz instancję Bar lub fabrykę używaną do tworzenia instancji Bar (lub jeden z 483 innych sposobów na zrobienie tego), będziesz mieć dostęp niezbędny do przeprowadzenia testu.

Przykład fabryki:

Biorąc pod uwagę klasę Foo napisaną w ten sposób:

public class Foo {
  private BarFactory barFactory;

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

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

w metodzie testowej możesz wstrzyknąć BarFactory w następujący sposób:

@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: jest to przykład tego, jak TDD może sterować projektem twojego kodu.

Csturtz
źródło
6
Czy można to zrobić bez modyfikowania klasy do testów jednostkowych?
pan
6
Bar bar = mock(Bar.class)zamiastBar bar = new Bar();
John B
7
Nie, że jestem świadomy. ale nie sugeruję modyfikowania klasy tylko do testów jednostkowych. To naprawdę rozmowa na temat czystego kodu i SRP. Lub .. czy to do metody foo () w klasie Foo należy zbudowanie obiektu Bar. Jeśli odpowiedź brzmi „tak”, to jest to szczegół implementacji i nie powinieneś się martwić testowaniem interakcji (zobacz odpowiedź @ Michaela). Jeśli odpowiedź brzmi „nie”, to modyfikujesz klasę, ponieważ trudność w testowaniu jest czerwoną flagą, że twój projekt wymaga niewielkiej poprawy (stąd bonus, który dodałem, dotyczy sposobu projektowania napędów TDD).
csturtz
3
Czy możesz przekazać „prawdziwy” obiekt do „weryfikacji” Mockito?
John B
4
Możesz także wyśmiewać fabrykę: BarFactory myFactory = mock(BarFactory.class); when(myFactory.createBar()).thenReturn(bar);
levsa
18

Klasyczna odpowiedź brzmi: „Ty nie”. Testujesz publiczny interfejs APIFoo , a nie jego elementy wewnętrzne.

Czy Fooma na to wpływ jakieś zachowanie obiektu (lub, co gorsza, jakiś inny obiekt w środowisku) foo()? Jeśli tak, sprawdź to. A jeśli nie, co robi metoda?

Michael Brewer-Davis
źródło
4
Więc co byś tutaj przetestował? Publiczny interfejs API Footo public void foo(), gdzie elementy wewnętrzne są powiązane tylko z paskiem.
behelit
15
Testowanie tylko publicznego API jest w porządku, dopóki nie pojawią się prawdziwe błędy z efektami ubocznymi, które wymagają testów. Na przykład sprawdzanie, czy metoda prywatna prawidłowo zamyka połączenia HTTP, jest nadmierne, dopóki nie odkryjesz, że metoda prywatna nie zamyka poprawnie swoich połączeń, a zatem powoduje ogromny problem. W tym momencie Mockito i stań verify()się bardzo pomocny, nawet jeśli nie czcisz już świętego ołtarza prób integracyjnych.
Dawngerpony,
@DuffJ Nie używam Javy, ale to brzmi jak coś, co powinien wykryć Twój kompilator lub narzędzie do analizy kodu.
user247702,
3
Zgadzam się z DuffJ, podczas gdy programowanie funkcjonalne jest fajne, przychodzi moment, w którym twój kod wchodzi w interakcję ze światem zewnętrznym. Nie ma znaczenia, czy nazwiesz to „wewnętrznymi”, „efektami ubocznymi” czy „funkcjonalnością”, zdecydowanie chcesz przetestować tę interakcję: jeśli to się zdarzy, i jeśli zdarzy się to poprawnie tyle razy i z poprawnymi argumentami. @Stijn: może to być zły przykład (ale jeśli należy otworzyć wiele połączeń i zamknąć tylko niektóre z nich, staje się to interesujące). Lepszym przykładem byłoby sprawdzenie pogody, czy prawidłowe dane zostałyby przesłane przez połączenie.
Andras Balázs Lajtha
13

Jeśli nie chcesz używać DI lub fabryk. Możesz refaktoryzować swoją klasę w nieco trudny sposób:

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
    }
}

A twoja klasa testowa:

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

Następnie klasa, która wywołuje twoją metodę foo, zrobi to w następujący sposób:

public class thirdClass {

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

Jak widać podczas wywoływania metody w ten sposób, nie trzeba importować klasy Bar do żadnej innej klasy, która wywołuje metodę foo, co może być czymś, czego chcesz.

Oczywiście wadą jest to, że pozwalasz dzwoniącemu na ustawienie obiektu Bar.

Mam nadzieję, że to pomoże.

raspacorp
źródło
3
Myślę, że to jest anty-wzór. Zależności powinny być wstrzykiwane, kropka. Zezwolenie na opcjonalnie wstrzykiwaną zależność wyłącznie w celu testowania umyślnie unika ulepszania kodu i celowo testuje coś innego niż kod działający w środowisku produkcyjnym. Oba są okropnymi, okropnymi rzeczami do zrobienia.
ErikE
8

Rozwiązanie dla twojego przykładowego kodu za pomocą PowerMockito.whenNew

  • mockito-all 1.10.8
  • rdzeń powermock 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();

    }

}

Wyjście JUnit Wyjście JUnit

javaPlease42
źródło
8

Myślę, że Mockito @InjectMocksjest właściwą drogą.

W zależności od intencji możesz użyć:

  1. Wtrysk konstruktora
  2. Zastrzyk ustawiacza właściwości
  3. Iniekcja

Więcej informacji w dokumentach

Poniżej znajduje się przykład z iniekcją polową:

Klasy:

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

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

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

Test:

@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
źródło
3

Tak, jeśli naprawdę chcesz / musisz to zrobić, możesz użyć PowerMock. Należy to uznać za ostateczność. Dzięki PowerMock możesz spowodować, że zwróci próbkę z wywołania do konstruktora. Następnie wykonaj weryfikację na próbnym. To powiedziawszy, odpowiedź csturtza jest „właściwa”.

Oto link do fałszywej konstrukcji nowych obiektów

John B.
źródło
0

Innym prostym sposobem byłoby dodanie instrukcji dziennika do bar.someMethod (), a następnie upewnienie się, że widzisz wspomniany komunikat po wykonaniu testu, zobacz przykłady tutaj: Jak wykonać JUnit w komunikacie w loggerze

Jest to szczególnie przydatne, gdy używana jest funkcja Bar.someMethod () private.

Nestor Milyaev
źródło