Wie man eine Abschlussklasse mit Mockito verspottet


218

Ich habe eine Abschlussklasse, so etwas wie:

public final class RainOnTrees{

   public void startRain(){

        // some code here
   }
}

Ich benutze diese Klasse in einer anderen Klasse wie dieser:

public class Seasons{

   RainOnTrees rain = new RainOnTrees();

   public void findSeasonAndRain(){

        rain.startRain();

    }
}

und in meiner JUnit-Testklasse Seasons.javamöchte ich die RainOnTreesKlasse verspotten . Wie kann ich das mit Mockito machen?


9
Mockito erlaubt es nicht, PowerMock jedoch.
fge

1
Ab Mockito 2.x unterstützt Mockito jetzt das Verspotten der endgültigen Klassen und Methoden.
Kent

Mögliches Duplikat der Mock-Abschlussklasse mit Mockito 2
Eliasah

Antworten:


155

Das Verspotten von endgültigen / statischen Klassen / Methoden ist nur mit Mockito v2 möglich.

füge dies in deine Gradle-Datei ein:

testImplementation 'org.mockito:mockito-inline:2.13.0'

Dies ist mit Mockito v1 in den Mockito-FAQ nicht möglich :

Was sind die Einschränkungen von Mockito

  • Benötigt Java 1.5+

  • Abschlussklassen können nicht verspottet werden

...


2
Dies hat bei mir in Scala nicht funktioniert (mit sbt-Modifikationen).
Micseydel

2
Das hat mir nicht gereicht. Ich musste auch src / test / resources / mockito-extensions / org.mockito.plugins.MockMaker mit "mock-maker-inline" gemäß baeldung.com/mockito-final
micseydel

204

Mockito 2 unterstützt jetzt endgültige Klassen und Methoden!

Aber im Moment ist das eine "Inkubations" -Funktion. Zum Aktivieren sind einige Schritte erforderlich, die unter Was ist neu in Mockito 2 beschrieben :

Das Verspotten von Abschlussklassen und -methoden ist eine inkubierende Opt-In-Funktion. Es verwendet eine Kombination aus Java-Agenteninstrumentierung und Unterklassen, um die Verspottbarkeit dieser Typen zu ermöglichen. Da dies anders funktioniert als unser aktueller Mechanismus und dieser unterschiedliche Einschränkungen aufweist und wir Erfahrungen und Benutzerfeedback sammeln möchten, musste diese Funktion explizit aktiviert werden, um verfügbar zu sein. Dies kann über den Mockito-Erweiterungsmechanismus erfolgen, indem eine Datei src/test/resources/mockito-extensions/org.mockito.plugins.MockMakermit einer einzelnen Zeile erstellt wird:

mock-maker-inline

Nachdem Sie diese Datei erstellt haben, verwendet Mockito automatisch diese neue Engine und es gibt folgende Möglichkeiten:

 final class FinalClass {
   final String finalMethod() { return "something"; }
 }

 FinalClass concrete = new FinalClass(); 

 FinalClass mock = mock(FinalClass.class);
 given(mock.finalMethod()).willReturn("not anymore");

 assertThat(mock.finalMethod()).isNotEqualTo(concrete.finalMethod());

In den folgenden Meilensteinen wird das Team eine programmatische Methode zur Verwendung dieser Funktion vorstellen. Wir werden alle nicht blockierbaren Szenarien identifizieren und unterstützen. Bleiben Sie dran und teilen Sie uns Ihre Meinung zu dieser Funktion mit!


14
Ich erhalte immer noch eine Fehlermeldung: Die Klasse android.content.ComponentName kann nicht verspottet werden. Mockito kann nicht verspotten / spionieren, weil: - letzte Klasse
IgorGanapolsky

3
Stellen Sie sicher, dass Sie die org.mockito.plugins.MockMakerDatei im richtigen Ordner ablegen.
WindRider

7
Ich
erhalte

8
@vCillusion Diese Antwort hat in keiner Weise mit PowerMock zu tun.
Linie

6
Ich habe diese Anweisungen befolgt, aber ich konnte diese Arbeit immer noch nicht ausführen. Musste jemand etwas anderes tun?
Franco

43

Sie können eine Abschlussklasse mit Mockito nicht verspotten, da Sie dies nicht alleine tun können.

Ich erstelle eine nicht endgültige Klasse, um die letzte Klasse zu verpacken und als Delegat zu verwenden. Ein Beispiel dafür ist TwitterFactoryKlasse, und dies ist meine verspottbare Klasse:

public class TwitterFactory {

    private final twitter4j.TwitterFactory factory;

    public TwitterFactory() {
        factory = new twitter4j.TwitterFactory();
    }

    public Twitter getInstance(User user) {
        return factory.getInstance(accessToken(user));
    }

    private AccessToken accessToken(User user) {
        return new AccessToken(user.getAccessToken(), user.getAccessTokenSecret());
    }

    public Twitter getInstance() {
        return factory.getInstance();
    }
}

Der Nachteil ist, dass es viel Boilerplate-Code gibt; Der Vorteil besteht darin, dass Sie einige Methoden hinzufügen können, die sich auf Ihr Anwendungsgeschäft beziehen können (z. B. die getInstance, die im obigen Fall einen Benutzer anstelle eines accessToken verwendet).

In Ihrem Fall würde ich eine nicht endgültige RainOnTreesKlasse erstellen , die an die letzte Klasse delegiert. Oder wenn Sie es nicht endgültig machen können, wäre es besser.


6
+1. Falls gewünscht, können Sie so etwas wie Lomboks verwenden @Delegate, um einen Großteil der Boilerplate zu handhaben.
Ruakh

2
@luigi können Sie als Beispiel ein Code-Snippet für Junit hinzufügen. Ich habe versucht, Wrapper für meine letzte Klasse zu erstellen, konnte aber nicht herausfinden, wie ich es testen soll.
Unglaublich

31

füge dies in deine Gradle-Datei ein:

testImplementation 'org.mockito:mockito-inline:2.13.0'

Dies ist eine Konfiguration, mit der Mockito mit Abschlussklassen funktioniert


1
Sollte jetzt wahrscheinlich "testImplementation" anstelle von "testCompile" verwenden. Gradle mag "testCompile" nicht mehr.
jwehrle

toller Kommentar, danke! bearbeitet zu testImplementation. Originalkommentar: testCompile 'org.mockito: mockito-inline: 2.13.0'
BennyP

2
Dies führt zu Fehlern bei der Ausführung unter Linux / OpenJDK 1.8:org.mockito.exceptions.base.MockitoInitializationException: Could not initialize inline Byte Buddy mock maker. (This mock maker is not supported on Android.)
naXa

Funktioniert gut, wenn auf Oracle JDK 1.8
naXa

23

Verwenden Sie Powermock. Dieser Link zeigt, wie es geht: https://github.com/jayway/powermock/wiki/MockFinal


30
Ich denke, PowerMock ist wie eines dieser Medikamente, die nur auf "verschreibungspflichtiger" Basis erhältlich sein sollten. Im Sinne von: Man sollte sehr deutlich machen, dass PowerMock viele Probleme hat; und dass es wie das ultimative letzte Mittel ist, es zu benutzen; und sollte so weit wie möglich vermieden werden.
GhostCat

1
Warum sagst du das?
PragmaticProgrammer

Ich habe mich über Powermockverspottende Abschlussklassen und statische Methoden lustig gemacht, um meine Abdeckung zu erhöhen, die offiziell überprüft wurde Sonarqube. Die Abdeckung betrug seit SonarQube 0%. Aus irgendeinem Grund werden Klassen, die Powermock verwenden, nicht erkannt. Ich habe mir und meinem Team einige Zeit genommen, um dies anhand eines Online-Threads zu realisieren. Das ist nur einer der Gründe, vorsichtig mit Powermock umzugehen und es wahrscheinlich nicht zu verwenden.
Amer

16

Nur um nachzufolgen. Bitte fügen Sie diese Zeile Ihrer Gradle-Datei hinzu:

testCompile group: 'org.mockito', name: 'mockito-inline', version: '2.8.9'

Ich habe verschiedene Versionen von Mockito-Core und Mockito-All ausprobiert. Keiner von ihnen arbeitet.


1
Um dies hinzuzufügen, habe ich Folgendes beobachtet: Wenn Sie Powermock zusammen mit Mockito verwenden; Das Hinzufügen der Mockmaker-Plugin-Datei in 'src / test / resources / mockito-extensions / org.mockito.plugins.MockMaker' wäre dann nicht hilfreich, um letzte Klassen zu verspotten. Stattdessen würde das Hinzufügen einer Abhängigkeit, wie oben von Michael_Zhang erwähnt, das Problem des Verspottens von Abschlussklassen lösen. Stellen Sie außerdem sicher, dass Sie Mockito 2 anstelle von Mockito1 verwenden
dishooom

12

Ich denke, Sie haben es geschafft, finalweil Sie verhindern möchten, dass andere Klassen erweitert werden RainOnTrees. Wie Effective Java vorschlägt (Punkt 15), gibt es eine andere Möglichkeit, eine Klasse für die Erweiterung nahe zu halten, ohne sie zu erstellen final:

  1. Entfernen Sie das finalSchlüsselwort.

  2. Machen Sie seinen Konstruktor private. Keine Klasse kann es erweitern, da sie den superKonstruktor nicht aufrufen kann .

  3. Erstellen Sie eine statische Factory-Methode, um Ihre Klasse zu instanziieren.

    // No more final keyword here.
    public class RainOnTrees {
    
        public static RainOnTrees newInstance() {
            return new RainOnTrees();
        }
    
    
        private RainOnTrees() {
            // Private constructor.
        }
    
        public void startRain() {
    
            // some code here
        }
    }

Mit dieser Strategie können Sie Mockito verwenden und Ihre Klasse zur Erweiterung mit wenig Boilerplate-Code geschlossen halten.


1
Dies funktioniert nicht für endgültige Methoden, die mit mockito 2 auch verspottet werden können.
Łukasz Rzeszotarski

11

Ich hatte das gleiche Problem. Da die Klasse, die ich verspotten wollte, eine einfache Klasse war, habe ich einfach eine Instanz davon erstellt und diese zurückgegeben.


2
Absolut, warum eine einfache Klasse verspotten? Spott ist für 'teure' Interaktionen: andere Dienste, Engines, Datenklassen usw.
StripLight

3
Wenn Sie eine Instanz davon erstellen, können Sie die Mockito.verify-Methoden danach nicht mehr darauf anwenden. Die Hauptanwendung von Mocks besteht darin, einige seiner Methoden testen zu können.
Riroo

6

Probieren Sie es aus:

Mockito.mock(SomeMockableType.class,AdditionalAnswers.delegatesTo(someInstanceThatIsNotMockableOrSpyable));

Es hat bei mir funktioniert. "SomeMockableType.class" ist die übergeordnete Klasse dessen, was Sie verspotten oder ausspionieren möchten, und someInstanceThatIsNotMockableOrSpyable ist die tatsächliche Klasse, die Sie verspotten oder ausspionieren möchten.

Weitere Details finden Sie hier


3
Es sollte beachtet werden, dass sich Delegierte stark von einheimischen Spionage-Spöttern unterscheiden. In einem nativen Mockito-Spion verweist "dies" in der Instanz auf den Spion selbst (da es sich um eine Unterklasse handelt). Im Delegaten ist "dies" jedoch das eigentliche Objekt someInstanceThatIsNotMockableOrSpyable. Nicht der Spion. Daher gibt es keine Möglichkeit, selbst aufrufende Funktionen zurückzugeben / zu überprüfen.
Dennis C

1
Können Sie ein Beispiel nennen?
Vishwa Ratna

5

Eine andere Problemumgehung, die in einigen Fällen zutreffen kann, besteht darin, eine Schnittstelle zu erstellen, die von dieser letzten Klasse implementiert wird, den Code so zu ändern, dass die Schnittstelle anstelle der konkreten Klasse verwendet wird, und dann die Schnittstelle zu verspotten. Auf diese Weise können Sie den Vertrag (Schnittstelle) von der Implementierung (letzte Klasse) trennen. Wenn Sie wirklich an die letzte Klasse binden möchten, gilt dies natürlich nicht.


5

Eigentlich gibt es einen Weg, den ich zum Spionieren benutze. Es würde für Sie nur funktionieren, wenn zwei Voraussetzungen erfüllt sind:

  1. Sie verwenden eine Art DI, um eine Instanz der letzten Klasse zu injizieren
  2. Die letzte Klasse implementiert eine Schnittstelle

Bitte erinnern Sie sich an Punkt 16 von Effective Java . Sie können einen Wrapper (nicht final) erstellen und alle Aufrufe an die Instanz der final class weiterleiten:

public final class RainOnTrees implement IRainOnTrees {
    @Override public void startRain() { // some code here }
}

public class RainOnTreesWrapper implement IRainOnTrees {
    private IRainOnTrees delegate;
    public RainOnTreesWrapper(IRainOnTrees delegate) {this.delegate = delegate;}
    @Override public void startRain() { delegate.startRain(); }
}

Jetzt können Sie Ihre letzte Klasse nicht nur verspotten, sondern auch ausspionieren:

public class Seasons{
    RainOnTrees rain;
    public Seasons(IRainOnTrees rain) { this.rain = rain; };
    public void findSeasonAndRain(){
        rain.startRain();
   }
}

IRainOnTrees rain = spy(new RainOnTreesWrapper(new RainOnTrees()) // or mock(IRainOnTrees.class)
doNothing().when(rain).startRain();
new Seasons(rain).findSeasonAndRain();

5

In Mockito 3 und mehr habe ich das gleiche Problem und habe es wie über diesen Link behoben

Mock Final Classes und Methoden mit Mockito wie folgt

Bevor Mockito zum Verspotten endgültiger Klassen und Methoden verwendet werden kann, muss es> konfiguriert werden.

Wir müssen eine Textdatei zum Verzeichnis src / test / resources / mockito-extensions des Projekts mit dem Namen org.mockito.plugins.MockMaker hinzufügen und eine einzelne Textzeile hinzufügen:

mock-maker-inline

Mockito überprüft das Erweiterungsverzeichnis beim Laden auf Konfigurationsdateien. Diese Datei ermöglicht das Verspotten der endgültigen Methoden und Klassen.


4

Zeitersparnis für Personen, die mit demselben Problem (Mockito + Final Class) auf Android + Kotlin konfrontiert sind. Wie in Kotlin sind Klassen standardmäßig endgültig. Ich habe eine Lösung in einem der Google Android-Beispiele mit Architekturkomponente gefunden. Hier ausgewählte Lösung: https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample

Erstellen Sie folgende Anmerkungen:

/**
 * This annotation allows us to open some classes for mocking purposes while they are final in
 * release builds.
 */
@Target(AnnotationTarget.ANNOTATION_CLASS)
annotation class OpenClass

/**
 * Annotate a class with [OpenForTesting] if you want it to be extendable in debug builds.
 */
@OpenClass
@Target(AnnotationTarget.CLASS)
annotation class OpenForTesting

Ändern Sie Ihre Gradle-Datei. Nehmen Sie ein Beispiel von hier: https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/build.gradle

apply plugin: 'kotlin-allopen'

allOpen {
    // allows mocking for classes w/o directly opening them for release builds
    annotation 'com.android.example.github.testing.OpenClass'
}

Jetzt können Sie jede Klasse mit Anmerkungen versehen, um sie zum Testen zu öffnen:

@OpenForTesting
class RepoRepository 

Dies funktioniert gut auf build.gradle auf App-Ebene, aber was können wir tun, um dies auf Bibliotheksebene zu erreichen?
Sumit T

Können Sie etwas näher darauf eingehen? Verwenden Sie normalerweise ein Fassadenmuster, um eine Verbindung zu libs herzustellen. Und verspotten Sie diese Fassadenklassen, um die App zu testen. Auf diese Weise müssen wir keine lib-Klassen verspotten.
Ozeetee

3

Dies ist möglich, wenn Sie Mockito2 mit der neuen Inkubationsfunktion verwenden, die das Verspotten der endgültigen Klassen und Methoden unterstützt.

Wichtige Punkte:
1. Erstellen Sie eine einfache Datei mit dem Namen "org.mockito.plugins.MockMaker" und legen Sie sie in einem Ordner mit dem Namen "mockito-extensions" ab. Dieser Ordner sollte im Klassenpfad verfügbar gemacht werden.
2. Der Inhalt der oben erstellten Datei sollte eine einzelne Zeile sein, wie unten angegeben:
mock-maker-inline

Die beiden oben genannten Schritte sind erforderlich, um den Mockito-Erweiterungsmechanismus zu aktivieren und diese Anmeldefunktion zu verwenden.

Beispielklassen sind wie folgt: -

FinalClass.java

public final class FinalClass {

public final String hello(){
    System.out.println("Final class says Hello!!!");
    return "0";
}

}}

Foo.java

public class Foo {

public String executeFinal(FinalClass finalClass){
    return finalClass.hello();
}

}}

FooTest.java

public class FooTest {

@Test
public void testFinalClass(){
    // Instantiate the class under test.
    Foo foo = new Foo();

    // Instantiate the external dependency
    FinalClass realFinalClass = new FinalClass();

    // Create mock object for the final class. 
    FinalClass mockedFinalClass = mock(FinalClass.class);

    // Provide stub for mocked object.
    when(mockedFinalClass.hello()).thenReturn("1");

    // assert
    assertEquals("0", foo.executeFinal(realFinalClass));
    assertEquals("1", foo.executeFinal(mockedFinalClass));

}

}}

Ich hoffe es hilft.

Kompletter Artikel hier verspottet-das-Unversöhnliche .


Sie sollten die Antwort hier einfügen und nicht auf eine externe Site verlinken. Wenn der Vorgang lang ist, können Sie eine Übersicht hinzufügen.
rghome

Bitte stellen Sie sicher, dass die folgenden Anmerkungen verwendet werden, wenn Sie @RunWith (PowerMockRunner.class) @PrepareForTest ({AFinalClass.class})
verspotten

1
@vCillusion - Das Beispiel, das ich gezeigt habe, verwendet nur die Mockito2-API. Mit der Opt-In-Funktion von Mockito2 kann man die endgültigen Klassen direkt verspotten, ohne Powermock verwenden zu müssen.
ksl

2

Ja, das gleiche Problem hier, wir können eine Abschlussklasse mit Mockito nicht verspotten. Um genau zu sein, kann Mockito Folgendes nicht verspotten / ausspionieren:

  • Abschlussklassen
  • anonyme Klassen
  • primitive Typen

Die Verwendung einer Wrapper-Klasse scheint mir jedoch ein hoher Preis zu sein. Holen Sie sich stattdessen PowerMockito.


2

Ich denke, Sie müssen im Prinzip mehr nachdenken. Stattdessen verwenden Sie als letzte Klasse seine Schnittstelle und die Scheinschnittstelle.

Dafür:

 public class RainOnTrees{

   fun startRain():Observable<Boolean>{

        // some code here
   }
}

hinzufügen

interface iRainOnTrees{
  public void startRain():Observable<Boolean>
}

und verspotten Sie Schnittstelle:

 @Before
    fun setUp() {
        rainService= Mockito.mock(iRainOnTrees::class.java)

        `when`(rainService.startRain()).thenReturn(
            just(true).delay(3, TimeUnit.SECONDS)
        )

    }

1

Bitte schauen Sie sich JMockit an . Es verfügt über eine umfangreiche Dokumentation mit vielen Beispielen. Hier haben Sie eine Beispiellösung für Ihr Problem (zur Vereinfachung habe ich einen Konstruktor hinzugefügt Seasons, um eine verspottete RainOnTreesInstanz einzufügen ):

package jmockitexample;

import mockit.Mocked;
import mockit.Verifications;
import mockit.integration.junit4.JMockit;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(JMockit.class)
public class SeasonsTest {

    @Test
    public void shouldStartRain(@Mocked final RainOnTrees rain) {
        Seasons seasons = new Seasons(rain);

        seasons.findSeasonAndRain();

        new Verifications() {{
            rain.startRain();
        }};
    }

    public final class RainOnTrees {
        public void startRain() {
            // some code here
        }

    }

    public class Seasons {

        private final RainOnTrees rain;

        public Seasons(RainOnTrees rain) {
            this.rain = rain;
        }

        public void findSeasonAndRain() {
            rain.startRain();
        }

    }
}

1

Lösungen von RC und Luigi R. Viggiano zusammen sind möglicherweise die beste Idee.

Obwohl Mockito nicht beabsichtigt ist , Abschlussklassen zu verspotten, ist der Delegierungsansatz möglich . Das hat seine Vorteile:

  1. Sie sind nicht gezwungen, Ihre Klasse in "nicht endgültig" zu ändern, wenn dies in erster Linie von Ihrer API beabsichtigt wird (endgültige Klassen haben ihre Vorteile ).
  2. Sie testen die Möglichkeit einer Dekoration um Ihre API.

In Ihrem Testfall leiten Sie die Anrufe absichtlich an das zu testende System weiter. Daher ist Ihre Dekoration von Natur aus nicht geeignet alles tun.

Daher kann Ihr Test auch zeigen, dass der Benutzer die API nur dekorieren kann, anstatt sie zu erweitern.

Subjektiver: Ich ziehe es vor, die Frameworks auf ein Minimum zu beschränken, weshalb JUnit und Mockito normalerweise für mich ausreichen. In der Tat zwingt mich die Einschränkung dieses Weges manchmal auch dazu, mich endgültig umzugestalten.


1

Wenn Sie versuchen , Unit-Test unter dem laufen Testordner ist die Top-Lösung in Ordnung. Folgen Sie einfach dem Hinzufügen einer Erweiterung.

Aber wenn Sie es mit Android-bezogenen Klassen wie Kontext oder Aktivität ausführen möchten, die sich im Android- Test- Ordner befinden, ist die Antwort für Sie.


1

Fügen Sie diese Abhängigkeiten hinzu, um mockito erfolgreich auszuführen:

testImplementation 'org.mockito: mockito-core: 2.24.5'
testImplementation "org.mockito: mockito-inline: 2.24.5"


0

Wie andere bereits gesagt haben, funktioniert dies mit Mockito nicht sofort. Ich würde vorschlagen, Reflection zu verwenden, um die spezifischen Felder für das Objekt festzulegen, das vom zu testenden Code verwendet wird. Wenn Sie dies häufig tun, können Sie diese Funktionalität in eine Bibliothek einbinden.

Abgesehen davon, wenn Sie derjenige sind, der die Klassen als endgültig markiert, hören Sie damit auf. Ich bin auf diese Frage gestoßen, weil ich mit einer API arbeite, bei der alles als endgültig markiert wurde, um meine berechtigte Notwendigkeit einer Erweiterung (Verspottung) zu verhindern, und ich wünschte, der Entwickler hätte nicht angenommen, dass ich die Klasse niemals erweitern müsste.


1
Öffentliche API-Klassen sollten zur Erweiterung geöffnet sein. Stimme voll und ganz zu. In einer privaten Codebasis finalsollte dies jedoch die Standardeinstellung sein.
ErikE

0

Für uns war es, weil wir Mockito-Inline vom Koin-Test ausgeschlossen haben. Ein Gradle-Modul benötigte dies tatsächlich und schlug aus gutem Grund nur bei Release-Builds fehl (Debug-Builds in der IDE funktionierten) :-P


0

Für die letzte Klasse fügen Sie unten hinzu, um statisch oder nicht statisch zu verspotten und aufzurufen.

1- diese Ebene in der Klasse hinzufügen @SuppressStatucInitializationFor (Wert = {Klassenname mit Paket})
2- PowerMockito.mockStatic (ClassName.class) wird Klasse verspotten
3- dann verwenden , wenn Anweisung Mock - Objekt zurück , wenn Methode dieser Klasse aufgerufen wird .

Genießen


-5

Ich habe nicht versucht, endgültig, aber für private, mit Reflexion entfernen Sie den Modifikator funktioniert! weiter geprüft haben, funktioniert es nicht für final.


Dies beantwortet nicht die gestellte Frage
Sanjit Kumar Mishra
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.