Eine Anleitung zu den Erwartungen von JMockit

1. Intro

Dieser Artikel ist der zweite Teil der JMockit-Serie. Vielleicht möchten Sie den Link lesen:/jmockit-101[erster Artikel], da wir davon ausgehen, dass Sie bereits mit den Grundlagen von JMockit vertraut sind.

Heute gehen wir tiefer und konzentrieren uns auf die Erwartungen. Wir zeigen, wie Sie einen spezifischeren oder allgemeineren Argumentabgleich definieren und erweiterte Möglichkeiten zur Definition von Werten.

2. Argumentwerte abgleichen

Die folgenden Ansätze gelten sowohl für Expectations als auch für Verifications .

2.1. "Beliebige" Felder

JMockit bietet eine Reihe von Hilfsfeldern, mit denen der Argumentabgleich generischer wird. Eines dieser Dienstprogramme sind die anyX -Felder.

Diese prüfen, ob ein Wert übergeben wurde und es gibt einen für jeden primitiven Typ (und die entsprechende Wrapper-Klasse), einen für Strings und einen "universellen" Typ vom Typ Object .

Sehen wir uns ein Beispiel an:

public interface ExpectationsCollaborator {
    String methodForAny1(String s, int i, Boolean b);
    void methodForAny2(Long l, List<String> lst);
}

@Test
public void test(@Mocked ExpectationsCollaborator mock) throws Exception {
    new Expectations() {{
        mock.methodForAny1(anyString, anyInt, anyBoolean);
        result = "any";
    }};

    Assert.assertEquals("any", mock.methodForAny1("barfooxyz", 0, Boolean.FALSE));
    mock.methodForAny2(2L, new ArrayList<>());

    new FullVerifications() {{
        mock.methodForAny2(anyLong, (List<String>) any);
    }};
}

Sie müssen berücksichtigen, dass Sie das Feld any in den erwarteten Typ umwandeln müssen. Die vollständige Liste der Felder finden Sie in der documentation .

2.2. Mit Methoden

JMockit stellt auch mehrere Methoden zur Verfügung, die beim generischen Argumentabgleich helfen. Das sind die withX -Methoden.

Diese ermöglichen ein etwas besseres Matching als die anyX -Felder.

Wir sehen hier ein Beispiel, in dem wir eine Erwartung für eine Methode definieren, die mit einer Zeichenfolge ausgelöst wird, die foo , eine Ganzzahl ungleich 1, einen nicht-null- Boolean und eine Instanz der List -Klasse enthält:

public interface ExpectationsCollaborator {
    String methodForWith1(String s, int i);
    void methodForWith2(Boolean b, List<String> l);
}

@Test
public void testForWith(@Mocked ExpectationsCollaborator mock) throws Exception {
    new Expectations() {{
        mock.methodForWith1(withSubstring("foo"), withNotEqual(1));
        result = "with";
    }};

    assertEquals("with", mock.methodForWith1("barfooxyz", 2));
    mock.methodForWith2(Boolean.TRUE, new ArrayList<>());

    new Verifications() {{
        mock.methodForWith2(withNotNull(), withInstanceOf(List.class));
    }};
}

Die vollständige Liste der withX -Methoden finden Sie unter http://jmockit.github.io/tutorial/mocking.html#expectation [documentation von JMockit.

Berücksichtigen Sie, dass das spezielle with (Delegate) und withArgThat (Matcher) in einem eigenen Unterabschnitt behandelt wird.

2.3. Null ist nicht Null

Früher als später ist es gut zu verstehen, dass null nicht dazu verwendet wird, ein Argument zu definieren, für das null an einen Mock übergeben wurde.

Tatsächlich wird null als syntaktischer Zucker verwendet, um zu definieren, dass ein Objekt übergeben wird (es kann also nur für Parameter des Referenztyps verwendet werden). Um genau zu überprüfen, ob ein bestimmter Parameter die Referenz null erhält, kann der Matcher withNull () verwendet werden.

Im nächsten Beispiel definieren wir das Verhalten für einen Mock, der ausgelöst werden sollte, wenn folgende Argumente angegeben werden: Zeichenfolge, Liste und null Verweis

public interface ExpectationsCollaborator {
    String methodForNulls1(String s, List<String> l);
    void methodForNulls2(String s, List<String> l);
}

@Test
public void testWithNulls(@Mocked ExpectationsCollaborator mock){
    new Expectations() {{
        mock.methodForNulls1(anyString, null);
        result = "null";
    }};

    assertEquals("null", mock.methodForNulls1("blablabla", new ArrayList<String>()));
    mock.methodForNulls2("blablabla", null);

    new Verifications() {{
        mock.methodForNulls2(anyString, (List<String>) withNull());
    }};
}

Beachten Sie den Unterschied: null bedeutet eine Liste und withNull () eine null -Referenz auf eine Liste. Dies vermeidet insbesondere die Notwendigkeit, den Wert in den deklarierten Parametertyp umzuwandeln (siehe, dass das dritte Argument umgewandelt werden musste, nicht das zweite).

Die einzige Bedingung, um dies verwenden zu können, ist, dass mindestens ein expliziter Argument-Matcher für die Erwartung verwendet wurde (entweder eine with -Methode oder ein any -Feld).

2.4. Feld "Zeiten"

Manchmal möchten wir die Anzahl der Aufrufe einschränken, die für eine gemachte Methode erwartet werden. Dafür hat JMockit die reservierten Wörter times , minTimes und maxTimes (alle drei lassen nur nicht negative Ganzzahlen zu).

public interface ExpectationsCollaborator {
    void methodForTimes1();
    void methodForTimes2();
    void methodForTimes3();
}

@Test
public void testWithTimes(@Mocked ExpectationsCollaborator mock) {
    new Expectations() {{
        mock.methodForTimes1(); times = 2;
        mock.methodForTimes2();
    }};

    mock.methodForTimes1();
    mock.methodForTimes1();
    mock.methodForTimes2();
    mock.methodForTimes3();
    mock.methodForTimes3();
    mock.methodForTimes3();

    new Verifications() {{
        mock.methodForTimes3(); minTimes = 1; maxTimes = 3;
    }};
}

In diesem Beispiel haben wir definiert, dass genau zwei Aufrufe (nicht eine, nicht drei, genau zwei) von methodForTimes1 () mit der Zeile times = 2; erfolgen sollen.

Dann haben wir das Standardverhalten verwendet (wenn keine Wiederholungseinschränkung angegeben ist minTimes = 1; wird verwendet), um zu definieren, dass mindestens ein Aufruf an __methodForTimes2 () erfolgt.

Schließlich haben wir mit minTimes = 1; gefolgt von maxTimes = 3; definiert, dass zwischen ein und drei Aufrufe methodForTimes3 () auftreten würden.

Berücksichtigen Sie, dass sowohl minTimes als auch maxTimes für dieselbe Erwartung angegeben werden können, solange minTimes zuerst zugewiesen wird. Andererseits können times nur alleine verwendet werden.

2.5. Benutzerdefinierte Argumentübereinstimmung

Manchmal ist das Abgleichen von Argumenten nicht so direkt wie das Angeben eines Werts oder die Verwendung einiger vordefinierter Dienstprogramme ( anyX oder withX ).

In diesen Fällen setzt JMockit auf die Matcher -Schnittstelle von http://hamcrest.org/ [Hamcrest®. Sie müssen lediglich einen Matcher für das bestimmte Testszenario definieren und diesen Matcher mit einem withArgThat () -Aufruf verwenden.

Sehen wir uns ein Beispiel für die Zuordnung einer bestimmten Klasse zu einem übergebenen Objekt an:

public interface ExpectationsCollaborator {
    void methodForArgThat(Object o);
}

public class Model {
    public String getInfo(){
        return "info";
    }
}

@Test
public void testCustomArgumentMatching(@Mocked ExpectationsCollaborator mock) {
    new Expectations() {{
        mock.methodForArgThat(withArgThat(new BaseMatcher<Object>() {
            @Override
            public boolean matches(Object item) {
                return item instanceof Model && "info".equals(((Model) item).getInfo());
            }

            @Override
            public void describeTo(Description description) { }
        }));
    }};
    mock.methodForArgThat(new Model());
}

3. Rückgabe von Werten

Betrachten wir nun die Rückgabewerte. Beachten Sie, dass die folgenden Ansätze nur für Expectations gelten, da für Verifications keine Rückgabewerte definiert werden können.

3.1. Ergebnis und Rückgabe (…​)

Bei der Verwendung von JMockit haben Sie drei Möglichkeiten, das erwartete Ergebnis des Aufrufs einer Mocked-Methode zu definieren. Von allen dreien sprechen wir jetzt über die ersten beiden (die einfachsten), die sicherlich 90% der alltäglichen Anwendungsfälle abdecken werden.

Dies sind das result -Feld und die returns (Object …​) -Methode:

  • Mit dem Feld result können Sie einen Rückgabewert für any definieren

nicht-void zurückgegebene Methode. Dieser Rückgabewert kann auch eine Ausnahme sein, die geworfen werden soll (dieses Mal für sowohl nicht-void als auch für void zurückgegebene Methoden).

  • ** Es können mehrere result -Feldzuweisungen vorgenommen werden, um zurückzukehren

  • mehr als ein Wert ** für mehr als einen Methodenaufruf (Sie können sowohl Rückgabewerte als auch Fehler mischen, die ausgelöst werden sollen).

  • ** Das gleiche Verhalten wird erreicht, wenn Sie result eine Liste zuweisen

oder ein Array von Werten (desselben Typs wie der Rückgabetyp der gespielten Methode, hier KEINE Ausnahmen).

Die Methode returns (Object …​) ist syntaktischer Zucker für die Rückgabe

mehrere Werte gleichzeitig.

Dies wird mit einem Code-Snippet einfacher dargestellt:

public interface ExpectationsCollaborator{
    String methodReturnsString();
    int methodReturnsInt();
}

@Test
public void testResultAndReturns(@Mocked Foo mock){
    new StrictExpectations() {{
        mock.methodReturnsString();
        result = "foo";
        result = new Exception();
        result = "bar";
        mock.methodReturnsInt(); result = new int[]{ 1, 2, 3 };
        mock.methodReturnsString(); returns("foo", "bar");
        mock.methodReturnsInt(); result = 1;
    }};

    assertEquals("Should return foo", "foo", mock.methodReturnsString());
    try {
        mock.methodReturnsString();
    } catch (Exception e) { }

    assertEquals("Should return bar", "bar", mock.methodReturnsString());
    assertEquals("Should return 1", 1, mock.methodReturnsInt());
    assertEquals("Should return 2", 2, mock.methodReturnsInt());
    assertEquals("Should return 3", 3, mock.methodReturnsInt());
    assertEquals("Should return foo", "foo", mock.methodReturnsString());
    assertEquals("Should return bar", "bar", mock.methodReturnsString());
    assertEquals("Should return 1", 1, mock.methodReturnsInt());
}

In diesem Beispiel haben wir definiert, dass für die ersten drei Aufrufe von methodReturnsString () die erwarteten Rückgaben (in Reihenfolge) "foo" , eine Ausnahme und "bar" sind. Dies wurde durch drei verschiedene Zuweisungen zum Feld result erreicht.

In Zeile 14 haben wir dann festgelegt, dass für den vierten und fünften Aufruf "foo" und "bar" mit der Methode returns (Object …​) zurückgegeben werden sollen.

Für methodReturnsInt () haben wir in Zeile 13 definiert, dass 1, 2 und zuletzt 3 zurückgegeben werden sollen, indem dem Feld result ein Array mit den unterschiedlichen Ergebnissen und in 15 ** 1 durch eine einfache Zuweisung an 1 festgelegt wird result feld.

Wie Sie sehen, gibt es mehrere Möglichkeiten, Rückgabewerte für überlegte Methoden zu definieren.

3.2. Delegatoren

Zum Abschluss des Artikels behandeln wir die dritte Art der Definition des Rückgabewerts: die Delegate -Schnittstelle. Diese Schnittstelle wird verwendet, um komplexere Rückgabewerte zu definieren, wenn übermittelte Methoden definiert werden.

Wir werden ein Beispiel sehen, um einfach die Erklärung zu erklären:

public interface ExpectationsCollaborator {
    Object methodForDelegate(int i);
}

@Test
public void testDelegate(@Mocked ExpectationsCollaborator mock) {
    new Expectations() {{

        mock.methodForDelegate(anyInt);
        result = new Delegate() {
            public int delegate(int i) throws Exception {
                if(i < 3) {
                    return 5;
                } else {
                    throw new Exception();
                }
            }
        };
    }};

    assertEquals("Should return 5", 5, mock.methodForDelegate(1));
    try {
        mock.methodForDelegate(3);
    } catch (Exception e) { }
}

Verwenden Sie einen Delegator, um eine neue Instanz für ihn zu erstellen und sie einem returns -Feld zuzuweisen. In dieser neuen Instanz sollten Sie eine neue Methode mit denselben Parametern und demselben Rückgabetyp erstellen wie die gemachte Methode (Sie können einen beliebigen Namen dafür verwenden). Verwenden Sie in dieser neuen Methode die gewünschte Implementierung, um den gewünschten Wert zurückzugeben.

In dem Beispiel haben wir eine Implementierung durchgeführt, in der 5 zurückgegeben werden sollte, wenn der an die gespielte Methode übergebene Wert kleiner als 3 ist und ansonsten eine Ausnahme ausgelöst wird (beachten Sie, dass wir times = 2; verwenden mussten, damit der zweite Aufruf erfolgt erwartet, da wir das Standardverhalten verloren haben, indem wir einen Rückgabewert definieren).

Es mag viel Code sein, aber in denselben Fällen ist es die einzige Möglichkeit, das gewünschte Ergebnis zu erzielen.

4. Fazit

Damit haben wir praktisch alles gezeigt, was wir brauchen, um Erwartungen und Nachweise für unsere täglichen Tests zu schaffen.

Natürlich veröffentlichen wir weitere Artikel zu JMockit.

Die vollständige Implementierung dieses Tutorials finden Sie wie immer unter https://github.com/eugenp/tutorials/tree/master/testing-modules/mocks.html des GitHub-Projekts.

4.1. Artikel in der Serie

Alle Artikel der Serie:

  • JMockit 101

  • Link:/Jmockit-Erwartungen[Ein Leitfaden für JMockit-Erwartungen]