web-dev-qa-db-de.com

mock oder Stub für verkettete Anrufe

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 zu testende Methode, 

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

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

48
jilen

Mockito kann mit verketteten Stubs 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 Mock zurück, das so eingerichtet ist, dass es Ihren Wert beim zweiten verketteten Methodenaufruf zurückgibt.

Mockitos Autoren weisen darauf hin, dass dies nur für älteren Code sein sollte. Andernfalls ist es besser, das Verhalten in Ihren CacheContext zu verschieben und alle Informationen bereitzustellen, die zur Ausführung des Jobs selbst erforderlich sind. Die Menge an Informationen, die Sie aus CacheContext abrufen, legt nahe, dass Ihre Klasse Feature Neid hat.

111
Lunivore

Ich fand, dass JMockit einfacher zu bedienen ist und vollständig darauf umgestellt wurde. Siehe Testfälle, die es verwenden:

https://github.com/ko5tik/andject/blob/master/src/test/Java/de/pribluda/Android/andject/ViewInjectionTest.Java

Hier spott ich die Activity-Basisklasse aus, die von Android SKD stammt und komplett Abgestumpft ist. Mit JMockit können Sie sich verspotten, was endgültig, privat, abstrakt oder was auch immer ist.

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))
3

Mein Vorschlag zur Vereinfachung Ihres Testfalls ist das Umgestalten Ihrer Methode.

Jedes Mal, wenn ich Probleme habe, eine Methode zu testen, ist es ein Codegeruch für mich, und ich frage mich, warum es schwierig ist, sie zu testen. Und wenn Code schwer zu testen ist, ist es wahrscheinlich schwierig zu verwenden und zu warten. 

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

3
Doug R

Nur für den Fall, dass Sie Kotlin verwenden. MockK sagt nichts über die Verkettung als schlechte Praxis aus und erlaubt es Ihnen leicht, dies zu tun.

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)
0
yuranos87