Mockito Wie man nur den Aufruf einer Methode der Oberklasse verspottet

94

Ich benutze Mockito in einigen Tests.

Ich habe folgende Klassen:

class BaseService {  
    public void save() {...}  
}

public Childservice extends BaseService {  
    public void save(){  
        //some code  
        super.save();
    }  
}   

Ich möchte nur den zweiten Aufruf ( super.save) von verspotten ChildService. Der erste Aufruf muss die reale Methode aufrufen. Gibt es eine Möglichkeit, das zu tun?

mada
quelle
Kann dies mit PowerMockito gelöst werden?
JavaPlease42
@ javaPlease42: Ja, Sie können: stackoverflow.com/a/23884011/2049986 .
Jacob van Lingen

Antworten:

57

Nein, Mockito unterstützt dies nicht.

Dies ist möglicherweise nicht die Antwort, nach der Sie suchen, aber was Sie sehen, ist ein Symptom dafür, dass Sie das Designprinzip nicht anwenden:

Bevorzugen Sie die Komposition gegenüber der Vererbung

Wenn Sie eine Strategie extrahieren, anstatt eine Superklasse zu erweitern, ist das Problem behoben.

Wenn Sie den Code jedoch nicht ändern dürfen, ihn aber trotzdem testen müssen, gibt es auf diese unangenehme Weise immer noch Hoffnung. Mit einigen AOP-Tools (zum Beispiel AspectJ) können Sie Code in die Super-Class-Methode einbinden und deren Ausführung vollständig vermeiden (yuck). Dies funktioniert nicht, wenn Sie Proxys verwenden. Sie müssen die Bytecode-Modifikation verwenden (entweder das Weben der Ladezeit oder das Weben der Kompilierungszeit). Es gibt spöttische Frameworks, die diese Art von Trick ebenfalls unterstützen, wie PowerMock und PowerMockito.

Ich schlage vor, Sie entscheiden sich für das Refactoring, aber wenn dies keine Option ist, werden Sie ernsthaften Hacking-Spaß haben.

iwein
quelle
5
Ich sehe die LSP-Verletzung nicht. Ich habe ungefähr das gleiche Setup wie das OP: eine Basis-DAO-Klasse mit einer findAll () -Methode und ein Unterklassen-DAO, das die Basismethode überschreibt, indem es super.findAll () aufruft und dann das Ergebnis sortiert. Die Unterklasse kann in alle Kontexte eingesetzt werden, die die Oberklasse akzeptieren. Verstehe ich deine Bedeutung falsch?
1
Ich werde die LSP-Bemerkung entfernen (sie bringt keinen Mehrwert für die Antwort).
iwein
Ja, Vererbung ist scheiße und das blöde Framework, an dem ich festhalte, ist mit Vererbung als einziger Option ausgestattet.
Sridhar Sarnobat
Angenommen, Sie können die Oberklasse nicht neu entwerfen, können Sie den //some codesCode in eine Methode extrahieren, die separat getestet werden kann.
Phasmal
1
OK habe es. Es ist ein anderes Problem als das, was ich zu lösen versuchte, als ich danach suchte, aber zumindest löste dieses Missverständnis mein eigenes Problem, den Aufruf der Basisklasse zu verspotten (den ich in der Tat nicht überschreibe).
Guillaume Perrot
86

Wenn Sie wirklich keine Wahl für das Refactoring haben, können Sie alles im Super-Methodenaufruf verspotten / stubben, z

    class BaseService {

        public void validate(){
            fail(" I must not be called");
        }

        public void save(){
            //Save method of super will still be called.
            validate();
        }
    }

    class ChildService extends BaseService{

        public void load(){}

        public void save(){
            super.save();
            load();
        }
    }

    @Test
    public void testSave() {
        ChildService classToTest = Mockito.spy(new ChildService());

        // Prevent/stub logic in super.save()
        Mockito.doNothing().when((BaseService)classToTest).validate();

        // When
        classToTest.save();

        // Then
        verify(classToTest).load();
    }
jgonian
quelle
2
Dieser Code würde den Aufruf von super.save () nicht wirklich verhindern. Wenn Sie also viel in super.save () tun, müssten Sie all diese Aufrufe verhindern ...
iwein
Eine fantastische Lösung wirkt Wunder für mich, wenn ich einen verspotteten Wert aus einer Superklassenmethode für das Kind zurückgeben möchte. Fantastisch, danke.
Gurnard
14
Dies funktioniert gut, es sei denn, die Validierung ist ausgeblendet oder die Speichermethode erledigt die Arbeit direkt, anstatt eine andere Methode aufzurufen. mockito nicht: Mockito.doNothing (). when ((BaseService) spy) .save (); Dies wird nicht 'nichts auf dem Basisdienst speichern, sondern auf dem childService speichern :(
tibi
Ich kann das bei mir nicht zum Laufen bringen - es stummschaltet auch die untergeordnete Methode. BaseServiceist abstrakt, obwohl ich nicht sehe, warum das relevant wäre.
Sridhar Sarnobat
1
@ Sridhar-Sarnobat Ja, ich sehe das Gleiche :( Jeder weiß, wie man es dazu super.validate()
bringt
4

Ziehen Sie in Betracht, den Code von der ChildService.save () -Methode in eine andere Methode umzugestalten und diese neue Methode zu testen, anstatt ChildService.save () zu testen. Auf diese Weise vermeiden Sie unnötigen Aufruf der Super-Methode.

Beispiel:

class BaseService {  
    public void save() {...}  
}

public Childservice extends BaseService {  
    public void save(){  
        newMethod();    
        super.save();
    }
    public void newMethod(){
       //some codes
    }
} 
Mohammed Misbahuddin
quelle
1

Erstellen Sie eine paketgeschützte Methode (setzt Testklasse in demselben Paket voraus) in der Unterklasse, die die Superklassenmethode aufruft, und rufen Sie diese Methode dann in Ihrer überschriebenen Unterklassenmethode auf. Sie können dann in Ihrem Test mithilfe des Spionagemusters Erwartungen an diese Methode setzen. Nicht schön, aber sicherlich besser, als sich mit allen Erwartungen an die Supermethode in Ihrem Test auseinandersetzen zu müssen

Luke
quelle
Kann ich auch sagen, dass Komposition über Vererbung fast immer besser ist, aber manchmal ist es einfach einfacher, Vererbung zu verwenden. Bis Java ein besseres Kompositionsmodell wie Scala oder Groovy enthält, wird dies immer der Fall sein und dieses Problem wird weiterhin bestehen
Luke
1

Auch wenn ich der Antwort von iwein voll und ganz zustimme (

Komposition gegenüber Vererbung bevorzugen

), ich gebe zu, es gibt Zeiten, in denen Vererbung nur natürlich erscheint, und ich fühle mich nicht gebrochen oder umgestaltet, nur um einen Unit-Test durchzuführen.

Also mein Vorschlag:

/**
 * BaseService is now an asbtract class encapsulating 
 * some common logic callable by child implementations
 */
abstract class BaseService {  
    protected void commonSave() {
        // Put your common work here
    }

    abstract void save();
}

public ChildService extends BaseService {  
    public void save() {
        // Put your child specific work here
        // ...

        this.commonSave();
    }  
}

Und dann im Unit-Test:

    ChildService childSrv = Mockito.mock(ChildService.class, Mockito.CALLS_REAL_METHODS);

    Mockito.doAnswer(new Answer<Void>() {
        @Override
        public Boolean answer(InvocationOnMock invocation)
                throws Throwable {
            // Put your mocked behavior of BaseService.commonSave() here
            return null;
        }
    }).when(childSrv).commonSave();

    childSrv.save();

    Mockito.verify(childSrv, Mockito.times(1)).commonSave();

    // Put any other assertions to check child specific work is done
dams50
quelle
0

Der Grund dafür ist, dass Ihre Basisklasse nicht öffentlich ist. Mockito kann sie aufgrund der Sichtbarkeit nicht abfangen. Wenn Sie die Basisklasse als öffentlich oder @Override in der Unterklasse (als öffentlich) ändern, kann Mockito sie korrekt verspotten.

public class BaseService{
  public boolean foo(){
    return true;
  }
}

public ChildService extends BaseService{
}

@Test
@Mock ChildService childService;
public void testSave() {
  Mockito.when(childService.foo()).thenReturn(false);

  // When
  assertFalse(childService.foo());
}
zoufeiyy
quelle
8
Dies ist nicht der Punkt. ChildService sollte foo () überschreiben und das Problem ist, wie BaseService.foo () verspottet wird, aber nicht ChildService.foo ()
Adriaan Koster
0

Wenn die Vererbung sinnvoll ist, ist es möglicherweise die einfachste Option, eine neue Methode (Paket privat ??) zu erstellen, um die Super-Methode aufzurufen (nennen wir sie superFindall), die reale Instanz auszuspionieren und dann die SuperFindAll () -Methode so zu verspotten, wie Sie es verspotten wollten die Elternklasse eins. Es ist nicht die perfekte Lösung in Bezug auf Abdeckung und Sichtbarkeit, aber es sollte den Job machen und es ist einfach anzuwenden.

 public Childservice extends BaseService {
    public void save(){
        //some code
        superSave();
    }

    void superSave(){
        super.save();
    }
}
Rubasace
quelle