2014-03-06 10 views
5

Angenommen delegiert Ich habe die folgende Klassein einem Unit-Test prüfen, ob alle Methoden

public abstract class Foo{ 

    public int bar(){ 
    //implementation 
    } 
    public abstract int bar2(); 
} 

und eine Basisklasse zu erleichtern Dekorateure für diese Klasse

public class FooWrapper{ 
    private final Foo delegate; 

    protected FooWrapper(Foo delegate){ 
    this.delegate = delegate; 
    } 
    @Override 
    public int bar(){ 
    return delegate.bar() 
    } 
    @Override 
    public int bar2(){ 
    return delegate.bar2(); 
    } 
} 

Die Klasse zu schreiben FooWrapper können Sie einen Dekorator für Foo schreiben, wo Sie nur die Methoden überschreiben, die Sie benötigen.

Jetzt möchte ich einen Test für FooWrapper schreiben, die überprüft, ob alle Methoden standardmäßig delegiert sind. Natürlich kann ich so etwas wie

@Test 
public void barShouldBeDelegated(){ 
    Foo delegate = Mockito.mock(Foo.class); 
    FooWrapper wrapper = new FooWrapper(delegate); 
    wrapper.bar(); 
    Mockito.verify(delegate).bar(); 
} 

schreiben Aber dies erfordert mir eine Methode, um Foo hinzugefügt wird, eine neue Testmethode jedes Mal hinzuzufügen. Ich hatte gehofft, einen Test zu haben, der jedes Mal fehlschlagen würde, wenn ich eine Methode zu Foo hinzufüge, die ich vergessen habe zu überschreiben und in FooWrapper delegieren.

Ich habe versucht, Reflexion zu verwenden, die mir erlaubt, jede Methode aufzurufen, aber ich weiß nicht, wie man überprüft, ob die Methode tatsächlich delegiert wird. Siehe das folgende Snippet für die Idee, die ich mit liebäugelt:

@Test 
    public void testAllMethodsAreDelegated() throws Exception{ 
    Foo delegate = mock(Foo.class); 
    FooWrapper wrapper = new FooWrapper(delegate); 

    Class<?> clazz = wrapper.getClass(); 
    Method[] methods = clazz.getDeclaredMethods(); 

    for (Method method : methods) { 
     Class<?>[] parameterTypes = method.getParameterTypes(); 
     Object[] arguments = new Object[parameterTypes.length]; 
     for (int j = 0; j < arguments.length; j++) { 
     arguments[j] = Mockito.mock(parameterTypes[j]); 
     } 
     method.invoke(wrapper, arguments); 

     // ?? how to verify whether the delegate is called 
     // Mockito.verify(delegate).??? does not work 
     // as I cannot specify the method by name 
     } 
    } 
    } 

Irgendwelche Ideen, ob es möglich ist, einen solchen Test zu schreiben. Beachten Sie, dass das einzige Mock-Framework, das ich verwenden kann, Mockito ist.

+0

Schöne Beschreibung und nette Idee :) –

+0

Konnten Sie etwas erhalten, das von der angenommenen Antwort funktioniert? Es hat einen ziemlich fatalen Fehler darin, dass es nicht wirklich überprüft, dass die Delegate-Methode aufgerufen wurde (siehe Kommentar unten). – Krease

+0

@Krease Ich habe die angenommene Antwort verwendet und es scheint zu funktionieren. Tests schlagen fehl, wenn der Wrapper den Delegaten nicht aufruft. Wenn Sie nur die gleichen Methoden im Wrapper und im Delegaten verwenden (was immer der Fall ist, wenn Sie sich gegenseitig erweitern), wird der Test fehlschlagen. Also ich bin mir nicht sicher, ob ich deine Bemerkung 100% verstehe. – Robin

Antwort

5

Dieser Code scheint den Trick zu tun. Wenn ich eine Methode zu Foo hinzufüge und sie nicht in FooWrapper einfüge, schlägt der Test fehl.

FooWrapper wrapper = new FooWrapper(delegate); 
    Foo delegate = Mockito.mock(Foo.class); 

    // For each method in the Foo class... 
    for (Method fooMethod : Foo.class.getDeclaredMethods()) { 
     boolean methodCalled = false; 

     // Find matching method in wrapper class and call it 
     for (Method wrapperMethod : FooWrapper.class.getDeclaredMethods()) { 
      if (fooMethod.getName().equals(wrapperMethod.getName())) { 

       // Get parameters for method 
       Class<?>[] parameterTypes = wrapperMethod.getParameterTypes(); 
       Object[] arguments = new Object[parameterTypes.length]; 
       for (int j = 0; j < arguments.length; j++) { 
        arguments[j] = Mockito.mock(parameterTypes[j]); 
       } 

       // Invoke wrapper method 
       wrapperMethod.invoke(wrapper, arguments); 

       // Ensure method was called on delegate exactly once with the correct arguments 
       fooMethod.invoke(Mockito.verify(delegate, Mockito.times(1)), arguments); 

       // Set flag to indicate that this foo method is wrapped properly. 
       methodCalled = true; 
      } 
     } 

     assertTrue("Foo method '" + fooMethod.getName() + "' has not been wrapped correctly in Foo wrapper", methodCalled); 
    } 

Der Schlüssel Linie hier, die im Code fehlte ist

fooMethod.invoke(Mockito.verify(delegate, Mockito.times(1)), arguments); 

Es könnte ein wenig seltsam aussehen, aber dies funktioniert, weil es Dinge in der gleichen Reihenfolge ruft Mockito erwartet: erste Mockito.verify(delegate) aufgerufen wird (was intern die Mockito-Verifikation startet), dann wird die Methode aufgerufen. Ein ähnlicher Nichtreflexionsaufruf würde wie Mockito.verify(delegate).foo() aussehen. Verwenden Sie dieses "Warum", um den Code an verschiedene Anwendungsfälle anzupassen, ohne zu brechen, wie der Test die Überprüfung durchführt.

Ein Wort der Vorsicht, ich würde einen Haken am Anfang jeder Ihrer Schleifen hinzufügen, die über das Ergebnis von getDeclaredMethods() iterieren. Diese Methode gibt alle Methoden zurück, ob sie öffentlich, privat, geschützt usw. sind. Der Versuch, auf eine nicht zugreifbare Methode zuzugreifen, löst eine Ausnahme aus. Sie können Method.isAccessible() verwenden, um dies zu überprüfen.

+0

Danke für die Warnung, aber ich habe diese Prüfung bereits gemacht. Habe es einfach nicht in Frage gestellt, es nicht zu lange zu machen. – Robin

+3

Bitte verwenden Sie nicht 'mal (1)' - Dies ist der Standard-Verifikationsmodus in Mockito, so dass es nur unnötig Rauschen in Ihrem Test wird. Diese Zeile, könnte einfach 'fooMethod.invoke (Mockito.verify (Delegat), Argumente) geschrieben werden;' –

+0

Ich war mir dessen nicht bewusst, danke für die Info David! –

Verwandte Themen