Inicjalizacja obiektów pozorowanych - MockIto

122

Istnieje wiele sposobów na zainicjowanie pozorowanego obiektu przy użyciu MockIto. Jaki jest najlepszy sposób spośród nich?

1.

 public class SampleBaseTestCase {

   @Before public void initMocks() {
       MockitoAnnotations.initMocks(this);
   }

2.

@RunWith(MockitoJUnitRunner.class)

[EDYCJA] 3.

mock(XXX.class);

zasugeruj mi, czy są inne sposoby lepsze niż te ...

VinayVeluri
źródło

Odpowiedzi:

153

W przypadku inicjalizacji mocków użycie runner lub the MockitoAnnotations.initMocksjest rozwiązaniami ściśle równoważnymi. Z javadoc MockitoJUnitRunner :

JUnit 4.5 runner initializes mocks annotated with Mock, so that explicit usage of MockitoAnnotations.initMocks(Object) is not necessary. Mocks are initialized before each test method.


Pierwszego rozwiązania (z MockitoAnnotations.initMocks) można użyć, gdy skonfigurowałeś już określonego runnera ( SpringJUnit4ClassRunnerna przykład) w swoim przypadku testowym.

Drugie rozwiązanie (z MockitoJUnitRunner) jest bardziej klasyczne i moje ulubione. Kod jest prostszy. Korzystanie z runnera zapewnia ogromną zaletę automatycznej walidacji użycia frameworka (opisanej przez @David Wallace w tej odpowiedzi ).

Oba rozwiązania pozwalają na dzielenie się fałszywkami (i szpiegami) między metodami testowymi. W połączeniu z @InjectMocks, pozwalają na bardzo szybkie pisanie testów jednostkowych. Zredukowano standardowy kod szyfrujący, testy są łatwiejsze do odczytania. Na przykład:

@RunWith(MockitoJUnitRunner.class)
public class ArticleManagerTest {

    @Mock private ArticleCalculator calculator;
    @Mock(name = "database") private ArticleDatabase dbMock;
    @Spy private UserProvider userProvider = new ConsumerUserProvider();

    @InjectMocks private ArticleManager manager;

    @Test public void shouldDoSomething() {
        manager.initiateArticle();
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        manager.finishArticle();
        verify(database).removeListener(any(ArticleListener.class));
    }
}

Zalety: kod jest minimalny

Wady: Czarna magia. IMO to głównie zasługa adnotacji @InjectMocks. Dzięki tej adnotacji „tracisz ból kodu” (zobacz świetne komentarze @Brice )


Trzecie rozwiązanie polega na stworzeniu makiety na każdej metodzie testowej. Pozwala, jak wyjaśnił @mlk w swojej odpowiedzi, na „ samodzielny test ”.

public class ArticleManagerTest {

    @Test public void shouldDoSomething() {
        // given
        ArticleCalculator calculator = mock(ArticleCalculator.class);
        ArticleDatabase database = mock(ArticleDatabase.class);
        UserProvider userProvider = spy(new ConsumerUserProvider());
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.initiateArticle();

        // then 
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        // given
        ArticleCalculator calculator = mock(ArticleCalculator.class);
        ArticleDatabase database = mock(ArticleDatabase.class);
        UserProvider userProvider = spy(new ConsumerUserProvider());
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.finishArticle();

        // then
        verify(database).removeListener(any(ArticleListener.class));
    }
}

Plusy: Wyraźnie pokazujesz, jak działa twój interfejs API (BDD ...)

Wady: jest więcej standardowych kodów. (Kreacja pozorów)


Moja rekomendacja to kompromis. Użyj @Mockadnotacji z @RunWith(MockitoJUnitRunner.class), ale nie używaj @InjectMocks:

@RunWith(MockitoJUnitRunner.class)
public class ArticleManagerTest {

    @Mock private ArticleCalculator calculator;
    @Mock private ArticleDatabase database;
    @Spy private UserProvider userProvider = new ConsumerUserProvider();

    @Test public void shouldDoSomething() {
        // given
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.initiateArticle();

        // then 
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        // given
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.finishArticle();

        // then 
        verify(database).removeListener(any(ArticleListener.class));
    }
}

Zalety: jasno pokazujesz, jak działa twój interfejs API (jak ArticleManagertworzony jest mój ). Brak kodu standardowego.

Wady: test nie jest samodzielny, mniej bólu związanego z kodem

Gontard
źródło
Uważaj jednak, adnotacje są przydatne, ale nie chronią Cię przed stworzeniem kiepskiego projektu OO (lub jego degradacji). Osobiście cieszę się, że mogę zredukować standardowy kod, ale tracę ból związany z kodem (lub PITA), który jest bodźcem do zmiany projektu na lepszy, więc ja i zespół zwracamy uwagę na projekt OO. Uważam, że podążanie za projektem OO z zasadami takimi jak SOLID projekt lub pomysły GOOS jest znacznie ważniejsze niż wybór sposobu tworzenia instancji.
Brice,
1
(ciąg dalszy) Jeśli nie widzisz, jak ten obiekt jest tworzony, nie czujesz z tego powodu bólu, a przyszli programiści mogą nie zareagować dobrze, jeśli należy dodać nową funkcjonalność. W każdym razie jest to dyskusyjne w obie strony, mówię tylko, aby zachować ostrożność.
Brice,
6
NIE JEST PRAWIDŁOWE, że te dwa są równoważne. NIE PRAWDA, że prostszy kod jest jedyną zaletą używania MockitoJUnitRunner. Aby uzyskać więcej informacji na temat różnic, zobacz pytanie na stackoverflow.com/questions/10806345/ ... i moją odpowiedź na nie.
Dawood ibn Kareem
2
@Gontard Tak, pewne zależności są widoczne, ale widziałem, jak kod poszedł źle przy użyciu tego podejścia. Jeśli chodzi o korzystanie z Collaborator collab = mock(Collaborator.class), moim zdaniem w ten sposób, z pewnością jest to słuszne podejście. Chociaż może to być rozwlekłe, możesz zyskać na zrozumiałości i poprawności testów. Oba sposoby mają swoje wady i zalety, nie zdecydowałem jeszcze, które podejście jest lepsze. Amyway zawsze można pisać bzdury i prawdopodobnie zależy to od kontekstu i kodera.
Brice
1
@mlk, całkowicie się z tobą zgadzam. Mój angielski nie jest zbyt dobry i brakuje w nim niuansów. Chodziło mi o to, aby nalegać na słowo UNIT.
gontard
30

Obecnie (od wersji 1.10.7) istnieje czwarty sposób tworzenia instancji makiet, który wykorzystuje regułę JUnit4 o nazwie MockitoRule .

@RunWith(JUnit4.class)   // or a different runner of your choice
public class YourTest
  @Rule public MockitoRule rule = MockitoJUnit.rule();
  @Mock public YourMock yourMock;

  @Test public void yourTestMethod() { /* ... */ }
}

JUnit szuka podklas TestRule z adnotacjami @Rule i używa ich do pakowania instrukcji testowych, które dostarcza Runner . W rezultacie możesz wyodrębnić metody @Before, metody @After, a nawet spróbować ... przechwycić opakowania do reguł. Możesz nawet wchodzić z nimi w interakcje z poziomu testu, tak jak robi to ExpectedException .

MockitoRule zachowuje się prawie tak samo, jak MockitoJUnitRunner , z tym wyjątkiem, że możesz użyć dowolnego innego programu uruchamiającego , takiego jak Sparametryzowany (który umożliwia konstruktorom testów pobieranie argumentów, dzięki czemu testy mogą być uruchamiane wiele razy) lub program uruchamiający testy Robolectric (dzięki czemu jego classloader może zapewnić zamienniki Java dla klas natywnych systemu Android). To sprawia, że ​​jest bardziej elastyczny w użyciu w najnowszych wersjach JUnit i Mockito.

W podsumowaniu:

  • Mockito.mock(): Bezpośrednie wywołanie bez obsługi adnotacji lub weryfikacji użycia.
  • MockitoAnnotations.initMocks(this): Obsługa adnotacji, brak weryfikacji użycia.
  • MockitoJUnitRunner: Obsługa adnotacji i walidacja użycia, ale musisz użyć tego elementu uruchamiającego.
  • MockitoRule: Obsługa adnotacji i walidacja użycia z dowolnym runner'em JUnit.

Zobacz też: Jak działa JUnit @Rule?

Jeff Bowman
źródło
3
W Kotlinie reguła wygląda tak:@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
Cristan
10

Jest na to zgrabny sposób.

  • Jeśli jest to test jednostkowy, możesz to zrobić:

    @RunWith(MockitoJUnitRunner.class)
    public class MyUnitTest {
    
        @Mock
        private MyFirstMock myFirstMock;
    
        @Mock
        private MySecondMock mySecondMock;
    
        @Spy
        private MySpiedClass mySpiedClass = new MySpiedClass();
    
        // It's gonna inject the 2 mocks and the spied object per reflection to this object
        // The java doc of @InjectMocks explains it really well how and when it does the injection
        @InjectMocks
        private MyClassToTest myClassToTest;
    
        @Test
        public void testSomething() {
        }
    }
    
  • EDYCJA: Jeśli jest to test integracji, możesz to zrobić (nie jest przeznaczone do używania w ten sposób w Spring. Pokaż tylko, że możesz zainicjować makiety z różnymi Runnerami):

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("aplicationContext.xml")
    public class MyIntegrationTest {
    
        @Mock
        private MyFirstMock myFirstMock;
    
        @Mock
        private MySecondMock mySecondMock;
    
        @Spy
        private MySpiedClass mySpiedClass = new MySpiedClass();
    
        // It's gonna inject the 2 mocks and the spied object per reflection to this object
        // The java doc of @InjectMocks explains it really well how and when it does the injection
        @InjectMocks
        private MyClassToTest myClassToTest;
    
        @Before
        public void setUp() throws Exception {
              MockitoAnnotations.initMocks(this);
        }
    
        @Test
        public void testSomething() {
        }
    }
    
emd
źródło
1
Jeśli MOCK jest również zaangażowany w testy integracyjne, czy ma to sens?
VinayVeluri
2
właściwie to nie będzie, twoje prawo. Chciałem tylko pokazać możliwości Mockito. Na przykład, jeśli używasz RESTFuse, musisz użyć ich runnera, abyś mógł zainicjować mocks za pomocą MockitoAnnotations.initMocks (this);
emd
8

Mockito Adnotacje i biegacz zostały dobrze omówione powyżej, więc zamierzam dorzucić moją podwójną pensję dla niekochanych:

XXX mockedXxx = mock(XXX.class);

Używam tego, ponieważ uważam, że jest to trochę bardziej opisowe i wolę (nie z prawej strony) testy jednostkowe, aby nie używać zmiennych składowych, ponieważ lubię, aby moje testy były (na tyle, na ile mogą) samodzielne.

Michael Lloyd Lee mlk
źródło
Czy jest jakaś inna zaleta w stosunku do używania makiety (XX.klasa) poza samodzielnym tworzeniem przypadku testowego?
VinayVeluri
O ile mi wiadomo, nie.
Michael Lloyd Lee mlk
3
Mniej magii, którą trzeba zrozumieć, aby przeczytać test. Deklarujesz zmienną i nadajesz jej wartość - bez adnotacji, refleksji itp.
Karu
2

Mały przykład dla JUnit 5 Jupiter, "RunWith" został usunięty. Teraz musisz używać rozszerzeń używając adnotacji "@ExtendWith".

@ExtendWith(MockitoExtension.class)
class FooTest {

  @InjectMocks
  ClassUnderTest test = new ClassUnderTest();

  @Spy
  SomeInject bla = new SomeInject();
}
fl0w
źródło
0

Pozostałe odpowiedzi są świetne i zawierają więcej szczegółów, jeśli ich chcesz / potrzebujesz.
Oprócz tego chciałbym dodać TL; DR:

  1. Wolę używać
    • @RunWith(MockitoJUnitRunner.class)
  2. Jeśli nie możesz (ponieważ używasz już innego biegacza), wolisz użyć
    • @Rule public MockitoRule rule = MockitoJUnit.rule();
  3. Podobny do (2), ale należy nie używać tego więcej:
    • @Before public void initMocks() { MockitoAnnotations.initMocks(this); }
  4. Jeśli chcesz użyć makiety tylko w jednym z testów i nie chcesz wystawiać go na inne testy w tej samej klasie testowej, użyj
    • X x = mock(X.class)

(1) i (2) i (3) wykluczają się wzajemnie.
(4) mogą być używane w połączeniu z innymi.

ogniwo
źródło