Mockito gegen EasyMock gegen JMockit
1. Einführung
1.1. Überblick
In diesem Beitrag werden wir übermocking sprechen: Was es ist, warum es verwendet wird und einige Beispiele, wie derselbe Testfall mit einigen der am häufigsten verwendeten Verspottungsbibliotheken für Java verspottet wird.
Wir beginnen mit einigen formalen / semi-formalen Definitionen von Spottkonzepten. Anschließend stellen wir den zu testenden Fall vor, geben Beispiele für jede Bibliothek und schließen daraus einige Schlussfolgerungen. Die ausgewählten Bibliotheken sindMockito,EasyMock undJMockit.
Wenn Sie das Gefühl haben, die Grundlagen des Verspottens bereits zu kennen, können Sie möglicherweise zu Punkt 2 übergehen, ohne die nächsten drei Punkte zu lesen.
1.2. Gründe für die Verwendung von Mocks
Wir gehen davon aus, dass Sie bereits nach einer bestimmten, auf Tests ausgerichteten Entwicklungsmethode (TDD, ATDD oder BDD) programmieren. Oder Sie möchten einfach einen Test für eine vorhandene Klasse erstellen, deren Funktionalität von Abhängigkeiten abhängt.
In jedem Fall möchten wir beim Unit-Test einer Klassetest only its functionality and not that of its dependencies (entweder weil wir ihrer Implementierung vertrauen oder weil wir sie selbst testen).
Um dies zu erreichen, müssen wir dem Prüfling einen Ersatz zur Verfügung stellen, den wir für diese Abhängigkeit steuern können. Auf diese Weise können wir extreme Rückgabewerte erzwingen, Ausnahmen auslösen oder einfach zeitaufwendige Methoden auf einen festen Rückgabewert reduzieren.
Diese kontrollierte Ersetzung istmock und hilft Ihnen, die Testcodierung zu vereinfachen und die Testausführungszeit zu verkürzen.
1.3. Scheinkonzepte und Definition
Sehen wir uns vier Definitionen aus einemarticlevon Martin Fowler an, die die Grundlagen zusammenfassen, die jeder über Mocks wissen sollte:
-
Dummy Objekte werden herumgereicht, aber nie tatsächlich verwendet. Normalerweise werden sie nur zum Füllen von Parameterlisten verwendet.
-
Fake Objekte haben funktionierende Implementierungen, verwenden jedoch normalerweise eine Verknüpfung, die sie für die Produktion nicht geeignet macht (eine In-Memory-Datenbank ist ein gutes Beispiel).
-
Stubs bieten vordefinierte Antworten auf Anrufe, die während des Tests getätigt wurden, und reagieren normalerweise überhaupt nicht auf etwas anderes als das, was für den Test programmiert wurde. Stubs zeichnen möglicherweise auch Informationen zu Anrufen auf, z. B. ein E-Mail-Gateway-Stub, der die gesendeten Nachrichten oder nur die Anzahl der gesendeten Nachrichten speichert.
-
Mocks sind das, worüber wir hier sprechen: Objekte, die mit Erwartungen vorprogrammiert sind und eine Spezifikation der Anrufe bilden, die sie voraussichtlich erhalten.
1.4 To Mock or Not to Mock: That Is the Question
Not everything must be mocked. Manchmal ist es besser, einen Integrationstest durchzuführen, da das Verspotten dieser Methode / Funktion nur für einen geringen tatsächlichen Nutzen funktioniert. In unserem Testfall (der im nächsten Punkt gezeigt wird) würde dies dieLoginDao testen.
DieLoginDao würden eine Bibliothek eines Drittanbieters für den DB-Zugriff verwenden, und das Verspotten würde nur darin bestehen, sicherzustellen, dass Parameter für den Aufruf vorbereitet wurden, aber wir müssten immer noch testen, ob der Aufruf die gewünschten Daten zurückgibt.
Aus diesem Grund wird es in diesem Beispiel nicht berücksichtigt (obwohl wir sowohl den Komponententest mit Scheinaufrufen für die Bibliotheksaufrufe von Drittanbietern als auch einen Integrationstest mit DBUnit zum Testen der tatsächlichen Leistung der Drittanbieter-Bibliothek schreiben könnten).
2. Testfall
Lassen Sie uns unter Berücksichtigung aller Aspekte des vorherigen Abschnitts einen recht typischen Testfall vorschlagen und wie wir ihn mit Mocks testen (wenn es sinnvoll ist, Mocks zu verwenden). Dies wird uns helfen, ein gemeinsames Szenario zu haben, um später die verschiedenen Spottbibliotheken vergleichen zu können.
2.1 Proposed Case
Der vorgeschlagene Testfall ist der Anmeldevorgang in einer Anwendung mit einer Schichtenarchitektur.
Die Anmeldeanforderung wird von einem Controller verarbeitet, der einen Dienst verwendet, der ein DAO verwendet (das nach Benutzeranmeldeinformationen in einer Datenbank sucht). Wir werden uns nicht zu sehr mit der Implementierung der einzelnen Ebenen befassen und uns mehr auf dieinteractions between the componentsjeder Ebene konzentrieren.
Auf diese Weise erhalten wir einLoginController, einLoginService und einLoginDAO. Sehen wir uns zur Verdeutlichung ein Diagramm an:
2.2 Implementation
Wir werden jetzt mit der Implementierung fortfahren, die für den Testfall verwendet wird, damit wir verstehen können, was bei den Tests passiert (oder was passieren sollte).
Wir beginnen mit dem für alle Operationen verwendeten ModellUserForm, das nur den Namen und das Kennwort des Benutzers enthält (wir verwenden zur Vereinfachung Modifikatoren für den öffentlichen Zugriff) und einer Getter-Methode für das Feldusername um Spott für diese Eigenschaft zuzulassen:
public class UserForm {
public String password;
public String username;
public String getUsername(){
return username;
}
}
Folgen wir mitLoginDAO, die keine Funktionalität mehr haben, da wir nur möchten, dass die Methoden vorhanden sind, damit wir sie bei Bedarf verspotten können:
public class LoginDao {
public int login(UserForm userForm){
return 0;
}
}
LoginDao wird vonLoginService in seinerlogin-Methode verwendet. LoginService hat auch einesetCurrentUser-Methode, dievoid zurückgibt, um diese Verspottung zu testen.
public class LoginService {
private LoginDao loginDao;
private String currentUser;
public boolean login(UserForm userForm) {
assert null != userForm;
int loginResults = loginDao.login(userForm);
switch (loginResults){
case 1:
return true;
default:
return false;
}
}
public void setCurrentUser(String username) {
if(null != username){
this.currentUser = username;
}
}
}
Schließlich verwendetLoginControllerLoginService für seinelogin-Methode. Dies beinhaltet:
-
In diesem Fall werden keine Anrufe an den verspotteten Dienst getätigt.
-
Ein Fall, in dem nur eine Methode aufgerufen wird.
-
Ein Fall, in dem alle Methoden aufgerufen werden.
-
Ein Fall, in dem das Ausnahmewerfen getestet wird.
public class LoginController {
public LoginService loginService;
public String login(UserForm userForm){
if(null == userForm){
return "ERROR";
}else{
boolean logged;
try {
logged = loginService.login(userForm);
} catch (Exception e) {
return "ERROR";
}
if(logged){
loginService.setCurrentUser(userForm.getUsername());
return "OK";
}else{
return "KO";
}
}
}
}
Nachdem wir gesehen haben, was wir testen möchten, wollen wir sehen, wie wir es mit jeder Bibliothek verspotten.
3. Versuchsaufbau
3.1 Mockito
Für Mockito verwenden wir Version2.8.9.
Der einfachste Weg, Mocks zu erstellen und zu verwenden, sind die Anmerkungen@Mock und@InjectMocks. Der erste erstellt einen Mock für die Klasse, die zum Definieren des Felds verwendet wird, und der zweite versucht, die erstellten Mocks in den mit Anmerkungen versehenen Mock einzufügen.
Es gibt weitere Anmerkungen wie@Spy, mit denen Sie einen Teil-Mock erstellen können (einen Mock, der die normale Implementierung in nicht verspotteten Methoden verwendet).
Davon abgesehen müssen SieMockitoAnnotations.initMocks(this) aufrufen, bevor Sie Tests ausführen, bei denen diese Mocks verwendet werden, damit all diese „Magie“ funktioniert. Dies erfolgt normalerweise in einer mit@Beforeannotierten Methode. Sie können auchMockitoJUnitRunner verwenden.
public class LoginControllerTest {
@Mock
private LoginDao loginDao;
@Spy
@InjectMocks
private LoginService spiedLoginService;
@Mock
private LoginService loginService;
@InjectMocks
private LoginController loginController;
@Before
public void setUp() {
loginController = new LoginController();
MockitoAnnotations.initMocks(this);
}
}
3.2 EasyMock
Für EasyMock verwenden wir die Version3.4 (Javadoc). Beachten Sie, dass Sie mit EasyMockEasyMock.replay(mock) für jede Testmethode aufrufen müssen, damit Mocks „funktionieren“. Andernfalls wird eine Ausnahme angezeigt.
Mocks und getestete Klassen können auch über Annotationen definiert werden. In diesem Fall verwenden wir jedoch anstelle einer statischen Methode dieEasyMockRunner für die Testklasse.
Mocks werden mit der Annotation@Mockund das getestete Objekt mit der Annotation@TestSubjecterstellt (deren Abhängigkeiten von erstellten Mocks eingefügt werden). Das getestete Objekt muss inline erstellt werden.
@RunWith(EasyMockRunner.class)
public class LoginControllerTest {
@Mock
private LoginDao loginDao;
@Mock
private LoginService loginService;
@TestSubject
private LoginController loginController = new LoginController();
}
3.3. JMockit
Für JMockit verwenden wir Version1.24 (Javadoc), da Version 1.25 noch nicht veröffentlicht wurde (zumindest während des Schreibens).
Das Setup für JMockit ist so einfach wie mit Mockito, mit der Ausnahme, dass es keine spezifische Anmerkung für Teil-Mocks gibt (und auch wirklich keine Notwendigkeit) und dass SieJMockit als Testläufer verwenden müssen.
Mocks werden mit der Annotation@Injectable(die nur eine Mock-Instanz erstellt) oder mit der Annotation@Mocked(die Mocks für jede Instanz der Klasse des mit Annotationen versehenen Felds erstellt) definiert.
Die getestete Instanz wird mithilfe der Annotation@Testederstellt (und ihre verspotteten Abhängigkeiten eingefügt).
@RunWith(JMockit.class)
public class LoginControllerTest {
@Injectable
private LoginDao loginDao;
@Injectable
private LoginService loginService;
@Tested
private LoginController loginController;
}
4. Überprüfen, ob keine Anrufe verspottet wurden
4.1. Mockito
Um zu überprüfen, ob ein Mock in Mockito keine Aufrufe erhalten hat, haben Sie die MethodeverifyZeroInteractions(), die einen Mock akzeptiert.
@Test
public void assertThatNoMethodHasBeenCalled() {
loginController.login(null);
Mockito.verifyZeroInteractions(loginService);
}
4.2. EasyMock
Um zu überprüfen, ob ein Mock keine Anrufe erhalten hat, geben Sie einfach kein Verhalten an. Sie spielen den Mock erneut ab und überprüfen ihn schließlich.
@Test
public void assertThatNoMethodHasBeenCalled() {
EasyMock.replay(loginService);
loginController.login(null);
EasyMock.verify(loginService);
}
4.3. JMockit
Um zu überprüfen, ob ein Mock keine Anrufe erhalten hat, geben Sie einfach keine Erwartungen für diesen Mock an und führen einFullVerifications(mock)für diesen Mock durch.
@Test
public void assertThatNoMethodHasBeenCalled() {
loginController.login(null);
new FullVerifications(loginService) {};
}
5. Definieren verspotteter Methodenaufrufe und Überprüfen von Verspottungsaufrufen
5.1. Mockito
Fürmocking method calls können SieMockito.when(mock.method(args)).thenReturn(value) verwenden. Hier können Sie verschiedene Werte für mehr als einen Aufruf zurückgeben, indem Sie sie einfach als weitere Parameter hinzufügen:thenReturn(value1, value2, value-n, …).
Beachten Sie, dass Sie mit dieser Syntax keine ungültigen Rückgabemethoden verspotten können. In diesen Fällen verwenden Sie eine Überprüfung dieser Methode (wie in Zeile 11 gezeigt).
Fürverifying calls zu einem Mock können SieMockito.verify(mock).method(args) verwenden und Sie können auch überprüfen, ob mitverifyNoMoreInteractions(mock) keine weiteren Aufrufe zu einem Mock mehr durchgeführt wurden.
Fürverifying args können Sie bestimmte Werte übergeben oder vordefinierte Matcher wieany(),anyString(),anyInt(). verwenden. Es gibt viel mehr solcher Matcher und sogar die Möglichkeit zu definieren Ihre Matcher, die wir in den folgenden Beispielen sehen werden.
@Test
public void assertTwoMethodsHaveBeenCalled() {
UserForm userForm = new UserForm();
userForm.username = "foo";
Mockito.when(loginService.login(userForm)).thenReturn(true);
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
Mockito.verify(loginService).login(userForm);
Mockito.verify(loginService).setCurrentUser("foo");
}
@Test
public void assertOnlyOneMethodHasBeenCalled() {
UserForm userForm = new UserForm();
userForm.username = "foo";
Mockito.when(loginService.login(userForm)).thenReturn(false);
String login = loginController.login(userForm);
Assert.assertEquals("KO", login);
Mockito.verify(loginService).login(userForm);
Mockito.verifyNoMoreInteractions(loginService);
}
5.2. EasyMock
Fürmocking method calls verwenden SieEasyMock.expect(mock.method(args)).andReturn(value).
Fürverifying calls zu einem Mock können SieEasyMock.verify(mock) verwenden, aber Sie müssen esalways after nennen undEasyMock.replay(mock) aufrufen.
Fürverifying args können Sie bestimmte Werte übergeben, oder Sie haben vordefinierte Matcher wie isA(Class.class),anyString(),anyInt() undlot more dieser Art von Matchern und wieder die Möglichkeit, Ihre Matcher zu definieren.
@Test
public void assertTwoMethodsHaveBeenCalled() {
UserForm userForm = new UserForm();
userForm.username = "foo";
EasyMock.expect(loginService.login(userForm)).andReturn(true);
loginService.setCurrentUser("foo");
EasyMock.replay(loginService);
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
EasyMock.verify(loginService);
}
@Test
public void assertOnlyOneMethodHasBeenCalled() {
UserForm userForm = new UserForm();
userForm.username = "foo";
EasyMock.expect(loginService.login(userForm)).andReturn(false);
EasyMock.replay(loginService);
String login = loginController.login(userForm);
Assert.assertEquals("KO", login);
EasyMock.verify(loginService);
}
5.3. JMockit
Mit JMockit haben Siesteps zum Testen definiert: Aufzeichnen, Wiedergeben und Überprüfen.
Record wird in einem neuenExpectations()\{\{}}-Block ausgeführt (in dem Sie Aktionen für mehrere Mocks definieren können).replay wird einfach durch Aufrufen einer Methode der getesteten Klasse ausgeführt (die einige verspottete aufrufen sollte Objekt), undverification wird in einem neuenVerifications()\{\{}}-Block ausgeführt (in dem Sie Überprüfungen für mehrere Mocks definieren können).
Fürmocking method calls können Siemock.method(args); result = value; in jedemExpectations-Block verwenden. Hier können Sie unterschiedliche Werte für mehr als einen Aufruf zurückgeben, indem Sie nurreturns(value1, value2, …, valuen); anstelle vonresult = value; verwenden.
Fürverifying calls zu einem Mock können Sie neue Überprüfungen()\{\{mock.call(value)}} odernew Verifications(mock)\{\{}} verwenden, um jeden zuvor definierten erwarteten Anruf zu überprüfen.
Fürverifying args können Sie bestimmte Werte übergeben, oder Sie habenpredefined values wieany,anyString,anyLong und viele weitere dieser Art von Sonderwerten und wieder die Möglichkeit, Ihre Matcher zu definieren (das müssen Hamcrest-Matcher sein).
@Test
public void assertTwoMethodsHaveBeenCalled() {
UserForm userForm = new UserForm();
userForm.username = "foo";
new Expectations() {{
loginService.login(userForm); result = true;
loginService.setCurrentUser("foo");
}};
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
new FullVerifications(loginService) {};
}
@Test
public void assertOnlyOneMethodHasBeenCalled() {
UserForm userForm = new UserForm();
userForm.username = "foo";
new Expectations() {{
loginService.login(userForm); result = false;
// no expectation for setCurrentUser
}};
String login = loginController.login(userForm);
Assert.assertEquals("KO", login);
new FullVerifications(loginService) {};
}
6. Verspottendes Ausnahmewerfen
6.1. Mockito
Das Auslösen von Ausnahmen kann mit.thenThrow(ExceptionClass.class) nachMockito.when(mock.method(args)) verspottet werden.
@Test
public void mockExceptionThrowin() {
UserForm userForm = new UserForm();
Mockito.when(loginService.login(userForm)).thenThrow(IllegalArgumentException.class);
String login = loginController.login(userForm);
Assert.assertEquals("ERROR", login);
Mockito.verify(loginService).login(userForm);
Mockito.verifyZeroInteractions(loginService);
}
6.2. EasyMock
Das Auslösen von Ausnahmen kann mit.andThrow(new ExceptionClass()) nach einem Aufruf vonEasyMock.expect(…) verspottet werden.
@Test
public void mockExceptionThrowing() {
UserForm userForm = new UserForm();
EasyMock.expect(loginService.login(userForm)).andThrow(new IllegalArgumentException());
EasyMock.replay(loginService);
String login = loginController.login(userForm);
Assert.assertEquals("ERROR", login);
EasyMock.verify(loginService);
}
6.3. JMockit
Das Verspotten von Ausnahmen mit JMockito ist besonders einfach. Geben Sie einfach eine Ausnahme als Ergebnis eines verspotteten Methodenaufrufs anstelle der "normalen" Rückkehr zurück.
@Test
public void mockExceptionThrowing() {
UserForm userForm = new UserForm();
new Expectations() {{
loginService.login(userForm); result = new IllegalArgumentException();
// no expectation for setCurrentUser
}};
String login = loginController.login(userForm);
Assert.assertEquals("ERROR", login);
new FullVerifications(loginService) {};
}
7. Verspotten eines Objekts zum Weitergeben
7.1. Mockito
Sie können einen Mock erstellen, der auch als Argument für einen Methodenaufruf übergeben wird. Mit Mockito können Sie dies mit einem Einzeiler tun.
@Test
public void mockAnObjectToPassAround() {
UserForm userForm = Mockito.when(Mockito.mock(UserForm.class).getUsername())
.thenReturn("foo").getMock();
Mockito.when(loginService.login(userForm)).thenReturn(true);
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
Mockito.verify(loginService).login(userForm);
Mockito.verify(loginService).setCurrentUser("foo");
}
7.2. EasyMock
Mocks können in Übereinstimmung mitEasyMock.mock(Class.class) erstellt werden. Anschließend können SieEasyMock.expect(mock.method()) verwenden, um es für die Ausführung vorzubereiten. Denken Sie dabei immer daran,EasyMock.replay(mock) aufzurufen, bevor Sie es verwenden.
@Test
public void mockAnObjectToPassAround() {
UserForm userForm = EasyMock.mock(UserForm.class);
EasyMock.expect(userForm.getUsername()).andReturn("foo");
EasyMock.expect(loginService.login(userForm)).andReturn(true);
loginService.setCurrentUser("foo");
EasyMock.replay(userForm);
EasyMock.replay(loginService);
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
EasyMock.verify(userForm);
EasyMock.verify(loginService);
}
7.3. JMockit
Um ein Objekt für nur eine Methode zu verspotten, können Sie es einfach als Parameter an die Testmethode übergeben. Dann können Sie Erwartungen wie bei jedem anderen Mock erzeugen.
@Test
public void mockAnObjectToPassAround(@Mocked UserForm userForm) {
new Expectations() {{
userForm.getUsername(); result = "foo";
loginService.login(userForm); result = true;
loginService.setCurrentUser("foo");
}};
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
new FullVerifications(loginService) {};
new FullVerifications(userForm) {};
}
8. Benutzerdefinierte Argumentübereinstimmung
8.1. Mockito
Manchmal muss die Argumentübereinstimmung für verspottete Aufrufe etwas komplexer sein als nur ein fester Wert oderanyString(). In diesen Fällen hat Mockito eine Matcher-Klasse, die mitargThat(ArgumentMatcher<>) verwendet wird.
@Test
public void argumentMatching() {
UserForm userForm = new UserForm();
userForm.username = "foo";
// default matcher
Mockito.when(loginService.login(Mockito.any(UserForm.class))).thenReturn(true);
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
Mockito.verify(loginService).login(userForm);
// complex matcher
Mockito.verify(loginService).setCurrentUser(ArgumentMatchers.argThat(
new ArgumentMatcher() {
@Override
public boolean matches(String argument) {
return argument.startsWith("foo");
}
}
));
}
8.2. EasyMock
Der benutzerdefinierte Argumentabgleich ist mit EasyMock etwas komplizierter, da Sie eine statische Methode erstellen müssen, in der Sie den tatsächlichen Matcher erstellen und ihn dann mitEasyMock.reportMatcher(IArgumentMatcher) melden.
Sobald diese Methode erstellt ist, verwenden Sie sie gemäß Ihrer Scheinerwartung mit einem Aufruf der Methode (wie im Beispiel in der Zeile gezeigt).
@Test
public void argumentMatching() {
UserForm userForm = new UserForm();
userForm.username = "foo";
// default matcher
EasyMock.expect(loginService.login(EasyMock.isA(UserForm.class))).andReturn(true);
// complex matcher
loginService.setCurrentUser(specificArgumentMatching("foo"));
EasyMock.replay(loginService);
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
EasyMock.verify(loginService);
}
private static String specificArgumentMatching(String expected) {
EasyMock.reportMatcher(new IArgumentMatcher() {
@Override
public boolean matches(Object argument) {
return argument instanceof String
&& ((String) argument).startsWith(expected);
}
@Override
public void appendTo(StringBuffer buffer) {
//NOOP
}
});
return null;
}
8.3. JMockit
Der benutzerdefinierte Argumentabgleich mit JMockit erfolgt mit der speziellen MethodewithArgThat(Matcher)(die die ObjekteMatchervonHamcrestempfängt).
@Test
public void argumentMatching() {
UserForm userForm = new UserForm();
userForm.username = "foo";
// default matcher
new Expectations() {{
loginService.login((UserForm) any);
result = true;
// complex matcher
loginService.setCurrentUser(withArgThat(new BaseMatcher() {
@Override
public boolean matches(Object item) {
return item instanceof String && ((String) item).startsWith("foo");
}
@Override
public void describeTo(Description description) {
//NOOP
}
}));
}};
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
new FullVerifications(loginService) {};
}
9. Teilweise Verspottung
9.1. Mockito
Mockito ermöglicht partielles Mocking (ein Mock, der in einigen seiner Methoden die reale Implementierung anstelle von gespielten Methodenaufrufen verwendet) auf zwei Arten.
Sie können entweder.thenCallRealMethod() in einer normalen Mock-Methodenaufrufdefinition verwenden oderspy anstelle eines Mocks erstellen. In diesem Fall besteht das Standardverhalten darin, die reale Implementierung in allen Nicht-Methoden aufzurufen. verspottete Methoden.
@Test
public void partialMocking() {
// use partial mock
loginController.loginService = spiedLoginService;
UserForm userForm = new UserForm();
userForm.username = "foo";
// let service's login use implementation so let's mock DAO call
Mockito.when(loginDao.login(userForm)).thenReturn(1);
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
// verify mocked call
Mockito.verify(spiedLoginService).setCurrentUser("foo");
}
9.2. EasyMock
Teilweise Verspottung wird mit EasyMock auch etwas komplizierter, da Sie definieren müssen, welche Methoden beim Erstellen der Verspottung verspottet werden.
Dies erfolgt mitEasyMock.partialMockBuilder(Class.class).addMockedMethod(“methodName”).createMock(). Sobald dies erledigt ist, können Sie das Mock wie jedes andere nicht-partielle Mock verwenden.
@Test
public void partialMocking() {
UserForm userForm = new UserForm();
userForm.username = "foo";
// use partial mock
LoginService loginServicePartial = EasyMock.partialMockBuilder(LoginService.class)
.addMockedMethod("setCurrentUser").createMock();
loginServicePartial.setCurrentUser("foo");
// let service's login use implementation so let's mock DAO call
EasyMock.expect(loginDao.login(userForm)).andReturn(1);
loginServicePartial.setLoginDao(loginDao);
loginController.loginService = loginServicePartial;
EasyMock.replay(loginDao);
EasyMock.replay(loginServicePartial);
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
// verify mocked call
EasyMock.verify(loginServicePartial);
EasyMock.verify(loginDao);
}
9.3. JMockit
Partielles Verspotten mit JMockit ist besonders einfach. Jeder Methodenaufruf, für den inExpectations()\{\{}} kein verspottetes Verhalten definiert wurde, verwendet die "echte" Implementierung.
In diesem Fall wird die tatsächliche Implementierung (und der Aufruf vonLoginDAO.login(UserForm)) ausgeführt, da fürLoginService.login(UserForm) keine Erwartung gegeben ist.
@Test
public void partialMocking() {
// use partial mock
LoginService partialLoginService = new LoginService();
partialLoginService.setLoginDao(loginDao);
loginController.loginService = partialLoginService;
UserForm userForm = new UserForm();
userForm.username = "foo";
// let service's login use implementation so let's mock DAO call
new Expectations() {{
loginDao.login(userForm); result = 1;
// no expectation for loginService.login
partialLoginService.setCurrentUser("foo");
}};
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
// verify mocked call
new FullVerifications(partialLoginService) {};
new FullVerifications(loginDao) {};
}
10. Fazit
In diesem Beitrag haben wir drei Java-Mock-Bibliotheken verglichen, von denen jede ihre Stärken und Nachteile hat.
-
Alle drei sindeasily configured mit Anmerkungen, mit denen Sie Mocks und das zu testende Objekt definieren können, und Läufer, um die Scheininjektion so schmerzfrei wie möglich zu gestalten.
-
Wir würden sagen, dass Mockito hier gewinnen würde, da es eine spezielle Anmerkung für Teilverspottungen gibt, aber JMockit benötigt sie nicht einmal. Nehmen wir also an, es ist ein Gleichstand zwischen diesen beiden.
-
-
Alle drei folgen mehr oder weniger denrecord-replay-verify pattern, aber unserer Meinung nach ist JMockit das Beste, da es Sie zwingt, diese in Blöcken zu verwenden, damit die Tests strukturierter werden.
-
Easiness der Verwendung ist wichtig, damit Sie so wenig wie möglich arbeiten können, um Ihre Tests zu definieren. JMockit wird die gewählte Option für seine feste, immer gleiche Struktur sein.
-
Mockito ist mehr oder weniger DAS bekannteste, so dass diecommunity größer sind.
-
replay jedes Mal aufrufen zu müssen, wenn Sie ein Mock verwenden möchten, ist ein klaresno-go, daher setzen wir für EasyMock ein Minus eins.
-
Consistency/simplicity ist auch für mich wichtig. Wir mochten die Art der Rückgabe von JMockit-Ergebnissen, die für „normale“ Ergebnisse genauso wie für Ausnahmen ist.
Wird dies alles gesagt, werden wirJMockit als eine Art Gewinner wählen, obwohl wir bisherMockito verwendet haben, da wir von seiner Einfachheit und festen Struktur fasziniert waren und werde versuchen, es von nun an zu verwenden.
Diefull implementation dieses Tutorials finden Sie aufthe GitHub project. Laden Sie sie herunter und spielen Sie damit.