Mockito vs EasyMock vs JMockit

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:

Test case diagram

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.