Mock oder Stub für verketteten Anruf

75
protected int parseExpire(CacheContext ctx) throws AttributeDefineException {
    Method targetMethod = ctx.getTargetMethod();
    CacheEnable cacheEnable = targetMethod.getAnnotation(CacheEnable.class);
    ExpireExpr cacheExpire = targetMethod.getAnnotation(ExpireExpr.class);
    // check for duplicate setting
    if (cacheEnable.expire() != CacheAttribute.DO_NOT_EXPIRE && cacheExpire != null) {
        throw new AttributeDefineException("expire are defined both in @CacheEnable and @ExpireExpr");
    }
    // expire time defined in @CacheEnable or @ExpireExpr
    return cacheEnable.expire() != CacheAttribute.DO_NOT_EXPIRE ? cacheEnable.expire() : parseExpireExpr(cacheExpire, ctx.getArgument());
}

das ist die Methode zu testen,

Method targetMethod = ctx.getTargetMethod();
CacheEnable cacheEnable = targetMethod.getAnnotation(CacheEnable.class);

Ich muss drei CacheContext, Method und CacheEnable verspotten. Gibt es eine Idee, den Testfall viel einfacher zu gestalten?

jilen
quelle

Antworten:

158

Mockito kann mit verketteten Stummeln umgehen :

Foo mock = mock(Foo.class, RETURNS_DEEP_STUBS);

// note that we're stubbing a chain of methods here: getBar().getName()
when(mock.getBar().getName()).thenReturn("deep");

// note that we're chaining method calls: getBar().getName()
assertEquals("deep", mock.getBar().getName());

AFAIK, die erste Methode in der Kette, gibt ein Modell zurück, das so eingerichtet ist, dass es Ihren Wert beim zweiten verketteten Methodenaufruf zurückgibt.

Die Autoren von Mockito weisen darauf hin, dass dies nur für Legacy-Code verwendet werden sollte . Andernfalls ist es besser, das Verhalten in Ihren CacheContext zu übertragen und alle Informationen bereitzustellen, die für die Ausführung des Auftrags selbst erforderlich sind. Die Menge an Informationen, die Sie aus CacheContext abrufen, deutet darauf hin, dass Ihre Klasse über Feature-Neid verfügt .

Lunivore
quelle
Nun, Szczepan hat Mockito erstellt, weil er gesehen hat, wie ich und einige andere unsere eigenen Mocks von Hand ausrollen, anstatt EasyMock zu verwenden, und beschlossen, dass Mocks für BDD besser funktionieren sollten - also bevorzuge ich natürlich Mockito! Aber er hat EasyMock dazu gezwungen, also ist EasyMock aus diesem Grund großartig. Wir stehen auf den Schultern der Riesen ...
Lunivore
Das ist interessant und ich habe gestimmt. Aber kannst du nicht einfach benutzen Foo foo=mock(Foo.class); Bar bar=mock(Bar.class); when(foo.getBar()).thenReturn(bar); when(bar.getName()).thenReturn("deep")'. Für mich ist das leicht zu lesen und erfordert kein Verständnis des Konzepts des "DEEP" -Stubbens. (
Übrigens
1
Dies funktioniert nicht, wenn eine der Ketten einen generischen Typ zurückgibt. Hat sich noch jemand diesem Problem gestellt?
Vivek Kothari
4
@Magnilex Es ist eigentlich in der offiziellen Quelle. Wie im Quellcode. "Bitte beachten Sie, dass in den meisten Szenarien ein Mock, der einen Mock zurückgibt, falsch ist." Außerdem: WARNUNG: Diese Funktion sollte für regulären sauberen Code selten erforderlich sein! Lassen Sie es für Legacy-Code. Verspotten eines Mocks, um einen Mock zurückzugeben, um einen Mock zurückzugeben, (...), um etwas Sinnvolles zurückzugeben, das auf einen Verstoß gegen das Demeter-Gesetz hinweist, oder um ein Wertobjekt zu verspotten (ein bekanntes Anti-Muster). github.com/mockito/mockito/blob/master/src/main/java/org/… (das meiste davon in Zeile 1393).
Lunivore
1
@RuifengMa Ich vermute aus dieser Dokumentation, dass es @Mock sein würde (answer = RETURNS_DEEP_STUBS) - probieren Sie es aus und lassen Sie es uns wissen! static.javadoc.io/org.mockito/mockito-core/2.2.28/org/mockito/…
Lunivore
5

Nur für den Fall, dass Sie Kotlin verwenden. MockK sagt nichts über Verkettungs eine schlechte Praxis zu sein und einfach können Sie tun dies .

val car = mockk<Car>()

every { car.door(DoorType.FRONT_LEFT).windowState() } returns WindowState.UP

car.door(DoorType.FRONT_LEFT) // returns chained mock for Door
car.door(DoorType.FRONT_LEFT).windowState() // returns WindowState.UP

verify { car.door(DoorType.FRONT_LEFT).windowState() }

confirmVerified(car)
Yuranos
quelle
Ja, großartige Sache, hilft wirklich dabei, die Menge an Boilerplate-Code zu reduzieren.
AbstractVoid
3

Mein Vorschlag, Ihren Testfall zu vereinfachen, besteht darin, Ihre Methode zu überarbeiten.

Immer wenn ich Probleme beim Testen einer Methode habe, riecht es nach Code und ich frage, warum es schwierig ist, sie zu testen. Und wenn Code schwer zu testen ist, ist er wahrscheinlich schwer zu verwenden und zu warten.

In diesem Fall liegt es daran, dass Sie eine Methodenkette haben, die mehrere Ebenen tief geht. Übergeben Sie möglicherweise ctx, cacheEnable und cacheExpire als Parameter.

Doug R.
quelle
Ja, aber diese Felder stammen zur Laufzeit aus dem aop-Kontext. Es ist schwierig, die Umgebung zu vereinfachen.
Jilen
Es gibt Techniken, um dies in JMockit zu tun. Sie können Felder in Ihre Objekte einbinden, um die AOP-Feldinjektion zu simulieren. Oder Sie können Entkapselungstechniken verwenden, die Testfelder mit verspotteten Instanzen initialisieren
Konstantin Pribluda,
Können Sie bitte ein Beispiel für @doug angeben?
ScottyBlades
1

Ich fand JMockit einfacher zu bedienen und komplett darauf umgestellt. Siehe Testfälle, die es verwenden:

https://github.com/ko5tik/andject/blob/master/src/test/java/de/pribluda/android/andject/ViewInjectionTest.java

Hier verspotte ich die Aktivitätsbasisklasse, die von Android SKD stammt und komplett gestoppt ist. Mit JMockit können Sie Dinge verspotten, die endgültig, privat, abstrakt oder was auch immer sind.

In Ihrem Testfall würde es so aussehen:

public void testFoo(@Mocked final Method targetMethod, 
                    @Mocked  final CacheContext context,
                    @Mocked final  CacheExpire ce) {
    new Expectations() {
       {
           // specify expected sequence of infocations here

           context.getTargetMethod(); returns(method);
       }
    };

    // call your method
    assertSomething(objectUndertest.cacheExpire(context))
Konstantin Pribluda
quelle
1
denkt über deine Antwort nach, aber ich benutze jedenfalls Mockito.
Jilen
1
Beachten Sie, dass JMockit eine Anmerkung speziell für verkettete Anrufe enthält : @Cascading. In solchen Fällen möchten Sie wahrscheinlich NonStrictExpectationsstattdessen verwenden Expectations, vorausgesetzt, die Aufrufe verspotteter Methoden müssen nicht überprüft werden.
Rogério
Danke, ich habe diese Anmerkung verpasst;) Meine Unit-Tests wurden vereinfacht
Konstantin Pribluda