Für die Mock-Initialisierung sind die Verwendung des Läufers oder der MockitoAnnotations.initMocks
streng ä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 ( SpringJUnit4ClassRunner
zum 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 @InjectMocks
ermö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 @Mock
Anmerkung 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 ArticleManager
instanziiert wird). Kein Boilerplate-Code.
Nachteile: Der Test ist nicht eigenständig, weniger Schmerzen im Code
MockitoJUnitRunner
. Weitere Informationen zu den Unterschieden finden Sie in der Frage unter stackoverflow.com/questions/10806345/… und meiner Antwort darauf.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.Es gibt jetzt (ab Version 1.10.7) eine vierte Möglichkeit, Mocks zu instanziieren, bei der eine JUnit4- Regel namens MockitoRule verwendet wird .
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?
quelle
@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
Es gibt eine gute Möglichkeit, dies zu tun.
Wenn es sich um einen Komponententest handelt, können Sie Folgendes tun:
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):
quelle
MockitoAnnotations & der Läufer wurden oben gut besprochen, also werde ich meine Tuppence für die Ungeliebten einwerfen:
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.
quelle
Als kleines Beispiel für JUnit 5 Jupiter wurde "RunWith" entfernt. Jetzt müssen Sie die Erweiterungen mit der Annotation "@ExtendWith" verwenden.
quelle
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:
@RunWith(MockitoJUnitRunner.class)
@Rule public MockitoRule rule = MockitoJUnit.rule();
@Before public void initMocks() { MockitoAnnotations.initMocks(this); }
X x = mock(X.class)
(1) und (2) und (3) schließen sich gegenseitig aus.
(4) kann in Kombination mit den anderen verwendet werden.
quelle