Ein Leitfaden zu JMockit-Erwartungen

1. Intro

Dieser Artikel ist die zweite Folge der JMockit-Reihe. Vielleicht möchten Sie den ersten Artikel lesen, da wir davon ausgehen, dass Sie bereits mit den Grundlagen von JMockit vertraut sind.

Heute werden wir tiefer gehen und uns auf die Erwartungen konzentrieren. Wir werden zeigen, wie Sie spezifischere oder allgemeinere Argumentabgleiche und erweiterte Methoden zum Definieren von Werten definieren.

2. Übereinstimmung der Argumentwerte

Die folgenden Ansätze gelten sowohl für Erwartungen als auch für Überprüfungen .

2.1. "Beliebige" Felder

JMockit bietet eine Reihe von Dienstprogrammfeldern, um die Argumentübereinstimmung allgemeiner zu gestalten. 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 Zeichenfolgen und einen "universellen" 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 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) any); }}; }

Sie müssen berücksichtigen, dass Sie bei Verwendung eines beliebigen Felds den erwarteten Typ verwenden müssen. Die vollständige Liste der Felder finden Sie in der Dokumentation.

2.2. "Mit" Methoden

JMockit bietet auch verschiedene Methoden zur Unterstützung des generischen Argumentabgleichs. Das sind die withX- Methoden.

Diese ermöglichen eine etwas erweiterte Übereinstimmung als die anyX- Felder. Wir können hier ein Beispiel sehen, in dem wir eine Erwartung für eine Methode definieren, die mit einer Zeichenfolge ausgelöst wird, die foo , eine Ganzzahl ungleich 1, einen Booleschen Wert ungleich Null und eine beliebige Instanz der List- Klasse enthält:

public interface ExpectationsCollaborator { String methodForWith1(String s, int i); void methodForWith2(Boolean b, List 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 in der Dokumentation von JMockit .

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

2.3. Null ist nicht Null

Früher oder später ist es gut zu verstehen, dass null nicht verwendet wird, um 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 (daher kann es nur für Parameter des Referenztyps verwendet werden). Um sicherzustellen , dass speziell ein gegebener Parameter empfängt die Null - Referenz, der withNull () kann Matcher verwendet werden.

Im nächsten Beispiel definieren wir das Verhalten für ein Modell, das ausgelöst werden soll, wenn folgende Argumente übergeben werden: Beliebige Zeichenfolge, beliebige Liste und eine Nullreferenz :

public interface ExpectationsCollaborator { String methodForNulls1(String s, List l); void methodForNulls2(String s, List l); } @Test public void testWithNulls(@Mocked ExpectationsCollaborator mock){ new Expectations() {{ mock.methodForNulls1(anyString, null); result = "null"; }}; assertEquals("null", mock.methodForNulls1("blablabla", new ArrayList())); mock.methodForNulls2("blablabla", null); new Verifications() {{ mock.methodForNulls2(anyString, (List) withNull()); }}; }

Man beachte den Unterschied: null bedeutet jede Liste und withNull () bedeutet einen Null Verweis auf eine Liste. Dies vermeidet insbesondere die Notwendigkeit, den Wert in den deklarierten Parametertyp umzuwandeln (siehe, dass das dritte Argument umgewandelt werden musste, nicht jedoch 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 beliebiges Feld).

2.4. Feld "Zeiten"

Manchmal möchten wir die Anzahl der Aufrufe beschränken, die für eine verspottete Methode erwartet werden. Zu diesem Zweck verfügt JMockit über die reservierten Wörter times , minTimes und maxTimes (alle drei erlauben nur nicht negative Ganzzahlen).

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 einer, nicht drei, genau zwei) von methodForTimes1 () mit den Zeilenzeiten = 2 ausgeführt werden sollen. .

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

Zuletzt mit minTimes = 1; gefolgt von maxTimes = 3; Wir haben definiert, dass zwischen ein und drei Aufrufe von 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 Zeiten nur alleine verwendet werden.

2.5. Benutzerdefinierte Argumentübereinstimmung

Manchmal ist die Argumentübereinstimmung nicht so direkt wie die einfache Angabe eines Werts oder die Verwendung einiger vordefinierter Dienstprogramme ( anyX oder withX ).

In diesen Fällen verlässt sich JMockit auf die Matcher- Oberfläche von Hamcrest . Sie müssen nur einen Matcher für das jeweilige 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() { @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. Werte zurückgeben

Schauen wir uns nun die Rückgabewerte an. Beachten Sie, dass die folgenden Ansätze nur für Erwartungen gelten, da für Überprüfungen keine Rückgabewerte definiert werden können .

3.1. Ergebnis und Rückgabe (…)

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

These two are the result field and the returns(Object…) method:

  • With the result field, you can define one return value for any non-void returning mocked method. This return value can also be an exception to be thrown (this time working for both non-void and void returning methods).
    • Several result field assignations can be done in order to return more than one value for more than one method invocations (you can mix both return values and errors to be thrown).
    • The same behaviour will be achieved when assigning to result a list or an array of values (of the same type than the return type of the mocked method, NO exceptions here).
  • The returns(Object…) method is syntactic sugar for returning several values of the same time.

This is more easily shown with a code snippet:

public interface ExpectationsCollaborator{ String methodReturnsString(); int methodReturnsInt(); } @Test public void testResultAndReturns(@Mocked ExpectationsCollaborator mock) { new Expectations() {{ mock.methodReturnsString(); result = "foo"; result = new Exception(); result = "bar"; returns("foo", "bar"); mock.methodReturnsInt(); result = new int[]{1, 2, 3}; result = 1; }}; assertEquals("Should return foo", "foo", mock.methodReturnsString()); try { mock.methodReturnsString(); fail("Shouldn't reach here"); } catch (Exception e) { // NOOP } 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 this example, we have defined that for the first three calls to methodReturnsString() the expected returns are (in order) “foo”, an exception and “bar”. We achieved this using three different assignations to the result field.

Then on line 14, we defined that for the fourth and fifth calls, “foo” and “bar” should be returned using the returns(Object…) method.

For the methodReturnsInt() we defined on line 13 to return 1, 2 and lastly 3 by assigning an array with the different results to the result field and on line 15 we defined to return 1 by a simple assignation to the result field.

As you can see there are several ways of defining return values for mocked methods.

3.2. Delegators

To end the article we're going to cover the third way of defining the return value: the Delegate interface. This interface is used for defining more complex return values when defining mocked methods.

We're going to see an example to simply the explaining:

public interface ExpectationsCollaborator { int methodForDelegate(int i); } @Test public void testDelegate(@Mocked ExpectationsCollaborator mock) { new Expectations() {{ mock.methodForDelegate(anyInt); result = new Delegate() { 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); fail("Shouldn't reach here"); } catch (Exception e) { } } 

The way to use a delegator is to create a new instance for it and assign it to a returns field. In this new instance, you should create a new method with the same parameters and return type than the mocked method (you can use any name for it). Inside this new method, use whatever implementation you want in order to return the desired value.

In the example, we did an implementation in which 5 should be returned when the value passed to the mocked method is less than 3 and an exception is thrown otherwise (note that we had to use times = 2; so that the second invocation is expected as we lost the default behaviour by defining a return value).

Es mag wie eine Menge Code erscheinen, aber in einigen Fällen ist dies der einzige Weg, um das gewünschte Ergebnis zu erzielen.

4. Fazit

Damit haben wir praktisch alles gezeigt, was wir brauchen, um Erwartungen und Überprüfungen für unsere täglichen Tests zu erstellen.

Wir werden natürlich mehr Artikel über JMockit veröffentlichen, also bleiben Sie dran, um noch mehr zu erfahren.

Und wie immer finden Sie die vollständige Implementierung dieses Tutorials im GitHub-Projekt.

4.1. Artikel in der Reihe

Alle Artikel der Reihe:

  • JMockit 101
  • Ein Leitfaden zu JMockit-Erwartungen
  • Erweiterte JMockit-Verwendung