Scheinobjekte initialisieren - MockIto

122

Es gibt viele Möglichkeiten, ein Scheinobjekt mit MockIto zu initialisieren. Was ist der beste Weg unter diesen?

1.

 public class SampleBaseTestCase {

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

2.

@RunWith(MockitoJUnitRunner.class)

[BEARBEITEN] 3.

mock(XXX.class);

Schlagen Sie mir vor, ob es andere Möglichkeiten gibt, die besser sind als diese ...

VinayVeluri
quelle

Antworten:

152

Für die Mock-Initialisierung sind die Verwendung des Läufers oder der MockitoAnnotations.initMocksstreng äquivalenten Lösungen. Aus dem Javadoc des 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.


Die erste Lösung (mit dem MockitoAnnotations.initMocks) kann verwendet werden, wenn Sie bereits einen bestimmten Läufer ( SpringJUnit4ClassRunnerzum Beispiel) für Ihren Testfall konfiguriert haben .

Die zweite Lösung (mit der MockitoJUnitRunner) ist die klassischere und meine Lieblingslösung. Der Code ist einfacher. Die Verwendung eines Runners bietet den großen Vorteil der automatischen Validierung der Framework-Nutzung (beschrieben von @David Wallace in dieser Antwort ).

Beide Lösungen ermöglichen es, die Verspottungen (und Spione) zwischen den Testmethoden zu teilen. In Verbindung mit dem @InjectMocksermöglichen sie das schnelle Schreiben von Unit-Tests. Der Kesselplatten-Verspottungscode wird reduziert, die Tests sind leichter zu lesen. Beispielsweise:

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

Vorteile: Der Code ist minimal

Nachteile: Schwarze Magie. IMO liegt es hauptsächlich an der Annotation @InjectMocks. Mit dieser Anmerkung "Sie verlieren den Schmerz des Codes" (siehe die großartigen Kommentare von @Brice )


Die dritte Lösung besteht darin, für jede Testmethode ein Modell zu erstellen. Es erlaubt, wie von @mlk in seiner Antwort erklärt, einen " in sich geschlossenen Test " zu haben.

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

Vorteile: Sie zeigen deutlich, wie Ihre API funktioniert (BDD ...)

Nachteile: Es gibt mehr Boilerplate-Code. (Die Verspottung Schöpfung)


Meine Empfehlung ist ein Kompromiss. Verwenden Sie die @MockAnmerkung mit dem @RunWith(MockitoJUnitRunner.class), aber nicht das @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));
    }
}

Vorteile: Sie zeigen deutlich, wie Ihre API funktioniert (wie meine ArticleManagerinstanziiert wird). Kein Boilerplate-Code.

Nachteile: Der Test ist nicht eigenständig, weniger Schmerzen im Code

Gontard
quelle
Seien Sie vorsichtig, die Anmerkungen sind nützlich, aber sie schützen Sie nicht davor, schlechtes OO-Design zu erstellen (oder es zu verschlechtern). Persönlich, während ich glücklich bin, den Boilerplate-Code zu reduzieren, verliere ich den Schmerz des Codes (oder PITA), der der Auslöser ist, um das Design in ein besseres zu ändern, also achten ich und das Team auf das OO-Design. Ich bin der Meinung, dass es viel wichtiger ist, dem OO-Design mit Prinzipien wie dem SOLID-Design oder den GOOS-Ideen zu folgen, als zu entscheiden, wie Mocks instanziiert werden sollen.
Brice
1
(Follow-up) Wenn Sie nicht sehen, wie dieses Objekt erstellt wird, spüren Sie den Schmerz nicht und zukünftige Programmierer reagieren möglicherweise nicht gut, wenn neue Funktionen hinzugefügt werden sollten. Wie auch immer, das ist in beide Richtungen fraglich, ich sage nur, um vorsichtig zu sein.
Brice
6
Es ist NICHT RICHTIG, dass diese beiden gleichwertig sind. Es ist NICHT WAHR, dass einfacherer Code der einzige Vorteil bei der Verwendung ist MockitoJUnitRunner. Weitere Informationen zu den Unterschieden finden Sie in der Frage unter stackoverflow.com/questions/10806345/… und meiner Antwort darauf.
Dawood ibn Kareem
2
@Gontard Ja, sicher, dass Abhängigkeiten sichtbar sind, aber ich habe gesehen, dass Code mit diesem Ansatz schief gelaufen ist. Über die Verwendung der Collaborator collab = mock(Collaborator.class)ist meiner Meinung nach dieser Weg sicherlich ein gültiger Ansatz. Während dies in der Regel ausführlich ist, können Sie die Verständlichkeit und Refaktorierbarkeit der Tests verbessern. Beide Wege haben ihre Vor- und Nachteile. Ich habe noch nicht entschieden, welcher Ansatz besser ist. Amyway ist es immer möglich Mist zu schreiben und hängt wahrscheinlich vom Kontext und dem Codierer ab.
Brice
1
@mlk Ich stimme dir vollkommen zu. Mein Englisch ist nicht sehr gut und es fehlen Nuancen. Mein Ziel war es, auf dem Wort UNIT zu bestehen.
Pontard
30

Es gibt jetzt (ab Version 1.10.7) eine vierte Möglichkeit, Mocks zu instanziieren, bei der eine JUnit4- Regel namens MockitoRule verwendet wird .

@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 sucht nach Unterklassen von TestRule, die mit @Rule kommentiert sind , und verwendet sie, um die vom Runner bereitgestellten Testanweisungen zu verpacken . Das Ergebnis ist, dass Sie @ Before-Methoden, @ After-Methoden extrahieren und sogar versuchen können, ... Wrapper in Regeln zu fangen. Sie können sogar innerhalb Ihres Tests mit diesen interagieren, so wie es ExpectedException tut.

MockitoRule verhält sich fast genau wie MockitoJUnitRunner , außer dass Sie jeden anderen Runner verwenden können, z. B. Parameterized (mit dem Ihre Testkonstruktoren Argumente verwenden können, damit Ihre Tests mehrmals ausgeführt werden können) oder Robolectrics Testrunner (damit sein Classloader Java-Ersatz bereitstellen kann für native Android-Klassen). Dies macht die Verwendung in neueren JUnit- und Mockito-Versionen streng flexibler.

Zusammenfassend:

  • Mockito.mock(): Direkter Aufruf ohne Annotationsunterstützung oder Nutzungsüberprüfung.
  • MockitoAnnotations.initMocks(this): Annotation-Unterstützung, keine Nutzungsüberprüfung.
  • MockitoJUnitRunner: Annotation-Unterstützung und Nutzungsüberprüfung, aber Sie müssen diesen Runner verwenden.
  • MockitoRule: Annotationsunterstützung und Nutzungsüberprüfung mit jedem JUnit-Runner.

Siehe auch: Wie funktioniert JUnit @Rule?

Jeff Bowman
quelle
3
In Kotlin sieht die Regel folgendermaßen aus:@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
Cristan
10

Es gibt eine gute Möglichkeit, dies zu tun.

  • Wenn es sich um einen Komponententest handelt, können Sie Folgendes tun:

    @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() {
        }
    }
  • BEARBEITEN: Wenn es sich um einen Integrationstest handelt, können Sie dies tun (nicht für die Verwendung mit Spring vorgesehen. Zeigen Sie einfach, dass Sie Mocks mit verschiedenen Läufern initialisieren können):

    @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
quelle
1
Wenn MOCK auch an Integrationstests beteiligt ist, ist dies sinnvoll?
VinayVeluri
2
Eigentlich wird es nicht, dein Recht. Ich wollte nur die Möglichkeiten von Mockito zeigen. Wenn Sie beispielsweise RESTFuse verwenden, müssen Sie deren Runner verwenden, damit Sie Mocks mit MockitoAnnotations.initMocks (this) initialisieren können.
emd
8

MockitoAnnotations & der Läufer wurden oben gut besprochen, also werde ich meine Tuppence für die Ungeliebten einwerfen:

XXX mockedXxx = mock(XXX.class);

Ich benutze dies, weil ich es ein bisschen aussagekräftiger finde und ich es vorziehe (nicht aus dem richtigen Verbot heraus) Unit-Tests, keine Mitgliedsvariablen zu verwenden, da ich möchte, dass meine Tests (so weit sie können) in sich geschlossen sind.

Michael Lloyd Lee mlk
quelle
Gibt es einen anderen Vorteil gegenüber der Verwendung von Mock (XX.Klasse), als den Testfall eigenständig zu machen?
VinayVeluri
Soweit mir bekannt ist.
Michael Lloyd Lee mlk
3
Weniger Magie, um verstehen zu müssen, um den Test zu lesen. Sie deklarieren die Variable und geben ihr einen Wert - keine Anmerkungen, Reflexionen usw.
Karu
1

Als kleines Beispiel für JUnit 5 Jupiter wurde "RunWith" entfernt. Jetzt müssen Sie die Erweiterungen mit der Annotation "@ExtendWith" verwenden.

@ExtendWith(MockitoExtension.class)
class FooTest {

  @InjectMocks
  ClassUnderTest test = new ClassUnderTest();

  @Spy
  SomeInject bla = new SomeInject();
}
fl0w
quelle
0

Die anderen Antworten sind großartig und enthalten mehr Details, wenn Sie sie wollen / brauchen.
Zusätzlich möchte ich eine TL hinzufügen; DR:

  1. Am liebsten verwenden
    • @RunWith(MockitoJUnitRunner.class)
  2. Wenn Sie nicht können (weil Sie bereits einen anderen Läufer verwenden), bevorzugen Sie die Verwendung
    • @Rule public MockitoRule rule = MockitoJUnit.rule();
  3. Ähnlich wie (2), aber Sie sollten dies nicht mehr verwenden:
    • @Before public void initMocks() { MockitoAnnotations.initMocks(this); }
  4. Wenn Sie ein Modell nur in einem der Tests verwenden und es nicht anderen Tests in derselben Testklasse aussetzen möchten, verwenden Sie
    • X x = mock(X.class)

(1) und (2) und (3) schließen sich gegenseitig aus.
(4) kann in Kombination mit den anderen verwendet werden.

neXus
quelle