wake-up-neo.net

Wie man mit Mockito eine letzte Klasse verspottet

Ich habe eine Abschlussklasse, etwa so:

public final class RainOnTrees{

   public void startRain(){

        // some code here
   }
}

Ich verwende 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 für Seasons.Java möchte ich die RainOnTrees-Klasse verspotten. Wie kann ich das mit Mockito machen?

122
buttowski

Das Verspotten endgültiger/statischer Klassen/Methoden ist nur mit Mockito v2 möglich.

Dies ist bei Mockito v1 nicht möglich, aus dem Mockito FAQ :

Was sind die Einschränkungen von Mockito?

  • Benötigt Java 1.5+

  • Schlusskurse können nicht gespielt werden

...

75
user180100

Mockito 2 unterstützt jetzt letzte Klassen und Methoden!

Für den Moment ist dies jedoch eine "Inkubationsfunktion". Zum Aktivieren sind einige Schritte erforderlich, die in What's New in Mockito 2 beschrieben werden: /:

Das Verspotten der letzten Klassen und Methoden ist eine incubating, Opt-In-Funktion. Es verwendet eine Kombination aus Java-Agenteninstrumentierung und Unterklassifizierung, um diese Typen mockbar zu machen. Da dies anders funktioniert als bei unserem derzeitigen Mechanismus, und dieser hat andere Einschränkungen. Da wir Erfahrung und Feedback von Benutzern sammeln möchten, musste diese Funktion explizit aktiviert werden, um verfügbar zu sein. Dies kann über den Mockito-Erweiterungsmechanismus erfolgen, indem die Datei src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker mit einer einzelnen Zeile erstellt wird:

mock-maker-inline

Nachdem Sie diese Datei erstellt haben, wird Mockito diese neue Engine automatisch verwenden, und Sie können Folgendes tun:

 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 nachfolgenden Meilensteinen wird das Team eine programmatische Verwendung dieser Funktion ermöglichen. Wir werden alle nicht zu übersetzenden Szenarien identifizieren und unterstützen. Bleiben Sie dran und lassen Sie uns wissen, was Sie von dieser Funktion halten!

135
WindRider

Sie können sich nicht mit Mockito über eine letzte Klasse lustig machen, da Sie dies nicht alleine tun können.

Was ich mache, ist das Erstellen einer nicht finalen Klasse, die die letzte Klasse umschließt und als Delegat verwendet. Ein Beispiel dafür ist TwitterFactory class, und dies ist meine spottbare 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 viele Code-Codes gibt. Der Vorteil ist, dass Sie einige Methoden hinzufügen können, die sich auf Ihr Anwendungsgeschäft beziehen können (z. B. die getInstance, die einen Benutzer anstelle eines AccessToken verwendet, im obigen Fall).

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

36

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

25
Gábor Lipták

fügen Sie dies in Ihre Gradle-Datei ein:

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

dies ist eine Konfiguration, mit der Mockito mit abschließenden Klassen arbeiten kann

17
BennyP

Nur um zu verfolgen. Bitte füge diese Zeile zu deiner 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.

12
Michael_Zhang

Ich denke, Sie haben es final erstellt, weil Sie verhindern möchten, dass andere Klassen RainOnTrees erweitern. Wie Effective Java schlägt vor (Punkt 15), gibt es eine andere Möglichkeit, eine Klasse für die Erweiterung nah zu halten, ohne sie final zu machen:

  1. Entfernen Sie das Schlüsselwort final.

  2. Machen Sie seinen Konstruktor private. Keine Klasse kann sie erweitern, da sie den Konstruktor super 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
        }
    }
    

Wenn Sie diese Strategie verwenden, können Sie Mockito verwenden und Ihre Klasse für die Erweiterung mit wenig Code für die Nebenplatine geschlossen halten.

11
Flávio Faria

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

6
user1945457

Probieren Sie es aus:

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

Es hat für mich 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.

Für weitere Details schauen Sie nach hier

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. Dadurch können Sie den Vertrag (die Schnittstelle) von der Implementierung (abschließende Klasse) trennen. Wenn Sie wirklich an die letzte Klasse binden möchten, gilt dies natürlich nicht.

4
andresp

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

  1. Sie verwenden eine Art DI, um eine Instanz der letzten Klasse einzufügen
  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-Klasse 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 kannst du nicht nur deine letzte Klasse 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();
4
xuesheng

Ja, dasselbe Problem hier, wir können uns nicht mit Mockito über eine letzte Klasse lustig machen. Um genau zu sein, kann Mockito Folgendes nicht ausspionieren:

  • abschlussklassen
  • anonyme Kurse
  • primitive Typen

Die Verwendung einer Wrapper-Klasse scheint mir jedoch ein großer Preis zu sein, also sollten Sie stattdessen PowerMockito kaufen.

2
Yu Chen

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

Wichtige Punkte zu beachten:
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 sein.
2. Der Inhalt der oben erstellten Datei sollte, wie unten angegeben, aus einer einzelnen Zeile bestehen:
Mock-Maker-Inline

Die obigen zwei Schritte sind erforderlich, um den Mockito-Erweiterungsmechanismus zu aktivieren und diese Opt-In-Funktion 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 Spott das Unmockbare .

2
ksl

Zeitersparnis für Personen, die auf Android + Kotlin das gleiche Problem (Mockito + Final Class) haben. Wie in Kotlin sind die Klassen standardmäßig endgültig. Ich habe in einem der Google Android-Beispiele mit der Architekturkomponente eine Lösung gefunden. Von 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 
2
Ozeetee

Bitte schauen Sie unter JMockit . Es verfügt über eine umfangreiche Dokumentation mit vielen Beispielen. Hier haben Sie eine beispielhafte Lösung für Ihr Problem (zur Vereinfachung habe ich Konstruktor zu Seasons hinzugefügt, um die gespielte RainOnTrees-Instanz zu injizieren):

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
Marcin

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

Obwohl Mockito nicht kann, ist der Delegationsansatz möglich,. Das hat seine Vorteile:

  1. Sie sind nicht gezwungen, Ihre Klasse in nicht final zu ändern, wenn dies in erster Linie von Ihrer API beabsichtigt ist (letzte 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. Aus diesem Grund tut Ihre Dekoration nicht alles.

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

Ein eher subjektiver Hinweis: Ich ziehe es vor, die Rahmen auf ein Minimum zu beschränken, weshalb mir JUnit und Mockito normalerweise ausreichen. In der Tat zwingt mich diese Einschränkung manchmal dazu, auch zum Guten umzustrukturieren.

1
Neel

Wie andere schon gesagt haben, funktioniert das mit Mockito nicht. Ich würde vorschlagen, Reflection zu verwenden, um die spezifischen Felder für das Objekt festzulegen, das vom getesteten 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 final markiert, hören Sie damit auf. Ich bin auf diese Frage gestoßen, weil ich mit einer API arbeite, bei der alles als final markiert wurde, um mein legitimes Bedürfnis nach Erweiterung (Spott) zu verhindern, und ich wünschte, der Entwickler hätte nicht angenommen, dass ich die Klasse niemals erweitern müsste.

0
Tom N.

Wenn Sie versuchen, Unit-Test unter dem Ordner test auszuführen, ist die beste Lösung in Ordnung. Folgen Sie einfach dem Hinzufügen einer Erweiterung.

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

0
Allen Wang

Für uns lag es daran, dass wir Mockito-Inline vom Koin-Test ausgeschlossen haben. Ein Gradle-Modul brauchte dies tatsächlich und scheiterte aus gutem Grund nur bei Release-Builds (Debug-Builds in IDE funktionierten) :-P

0
kenyee

Ich denke, Sie müssen im Prinzip mehr nachdenken. Stattdessen verwenden Sie in der Abschlussklasse stattdessen sein Interface und sein Mock-Interface.

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

    }
0
Serg Burlaka