Come deridere un'ultima classe con mockito


218

Ho una lezione finale, qualcosa del genere:

public final class RainOnTrees{

   public void startRain(){

        // some code here
   }
}

Sto usando questa classe in un'altra classe come questa:

public class Seasons{

   RainOnTrees rain = new RainOnTrees();

   public void findSeasonAndRain(){

        rain.startRain();

    }
}

e nella mia classe di test JUnit per Seasons.javavoglio deridere la RainOnTreesclasse. Come posso farlo con Mockito?


9
Mockito non lo consente, tuttavia PowerMock lo fa.
fge,

1
A partire da Mockito 2.x, Mockito ora supporta la derisione di classi e metodi finali.
Kent,

Possibile duplicato della classe finale Mock con Mockito 2
eliasah

Risposte:


156

Deridere classi / metodi finali / statici è possibile solo con Mockito v2.

aggiungi questo nel tuo file gradle:

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

Questo non è possibile con Mockito v1, dalle FAQ di Mockito :

Quali sono i limiti di Mockito

  • Ha bisogno di java 1.5+

  • Impossibile deridere le classi finali

...


2
Questo non ha funzionato per me in Scala (con modifiche sbt).
micseydel,

2
Questo non era abbastanza per me. Ho anche dovuto creare src / test / resources / mockito-extensions / org.mockito.plugins.MockMaker con "mock-maker-inline" come da baeldung.com/mockito-final
micseydel

205

Mockito 2 ora supporta classi e metodi finali !

Ma per ora questa è una funzione di "incubazione". Richiede alcuni passaggi per attivarlo, descritti in Novità di Mockito 2 :

La derisione delle classi e dei metodi finali è una caratteristica di incubazione , opt-in. Utilizza una combinazione di strumentazione e sottoclasse degli agenti Java per abilitare la derisione di questi tipi. Dato che funziona diversamente dal nostro attuale meccanismo e questo ha diversi limiti e poiché vogliamo raccogliere esperienza e feedback degli utenti, questa funzione doveva essere esplicitamente attivata per essere disponibile; può essere fatto tramite il meccanismo di estensione mockito creando il file src/test/resources/mockito-extensions/org.mockito.plugins.MockMakercontenente una singola riga:

mock-maker-inline

Dopo aver creato questo file, Mockito utilizzerà automaticamente questo nuovo motore e si può fare:

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

Nelle tappe successive, il team introdurrà un modo programmatico di utilizzare questa funzione. Identificheremo e forniremo supporto per tutti gli scenari non modificabili. Resta sintonizzato e facci sapere cosa ne pensi di questa funzione!


14
Ricevo ancora un errore: Impossibile
IgorGanapolsky

3
Assicurati di inserire il org.mockito.plugins.MockMakerfile nella cartella corretta.
WindRider

7
Ricevo anche l'errore anche dopo aver seguito quanto sopra menzionato: Mockito non può prendere in giro / spia perché: - classe finale
rcde0

8
@vCillusion questa risposta non è in alcun modo correlata a PowerMock.
Linea

6
Ho seguito queste istruzioni ma non riuscivo ancora a far funzionare questo, qualcuno doveva fare altro?
Franco,

43

Non puoi deridere un'ultima classe con Mockito, poiché non puoi farlo da solo.

Quello che faccio è creare una classe non finale per avvolgere la classe finale e usarla come delegata. Un esempio di questo è la TwitterFactoryclasse, e questa è la mia classe beffarda:

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();
    }
}

Lo svantaggio è che esiste un sacco di codice del boilerplate; il vantaggio è che è possibile aggiungere alcuni metodi che potrebbero essere correlati all'attività dell'applicazione (come getInstance che richiede un utente anziché un accessoToken, nel caso precedente).

Nel tuo caso, creerei una RainOnTreesclasse non finale da delegare alla classe finale. Oppure, se riesci a renderlo non definitivo, sarebbe meglio.


6
+1. Se lo si desidera, è possibile utilizzare qualcosa come Lombok @Delegateper gestire gran parte della piastra della caldaia.
Ruakh,

2
@luigi puoi aggiungere lo snippet di codice per Junit come esempio. Ho provato a creare Wrapper per la mia classe finale, ma non sono riuscito a tirarmi fuori, come testarlo.
Incredibile

31

aggiungi questo nel tuo file gradle:

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

questa è una configurazione per far funzionare mockito con le classi finali


1
Probabilmente dovrebbe usare "testImplementation" ora invece di "testCompile". A Gradle non piace più "testCompile".
jwehrle,

ottimo commento, grazie! modificato per testare l'implementazione. commento originale: testCompile 'org.mockito: mockito-inline: 2.13.0'
BennyP

2
Ciò provoca errori durante l'esecuzione su 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

Funziona bene quando si passa a Oracle JDK 1.8
naXa

23

Usa Powermock. Questo link mostra come farlo: https://github.com/jayway/powermock/wiki/MockFinal


30
Penso che PowerMock sia come uno di quei medicinali che dovrebbero uscire solo su base "da prescrizione". Nel senso di: si dovrebbe chiarire che PowerMock ha molti problemi; e che usarlo è come l'ultima risorsa ultima; e dovrebbe essere evitato il più possibile.
GhostCat

1
Perché dici questo?
PragmaticProgrammer,

Stavo usando Powermockper deridere le classi finali e i metodi statici per aumentare la mia copertura ufficialmente verificata Sonarqube. La copertura era dello 0% da SonarQube, per qualsiasi motivo la ragione non riconosce le classi che usano Powermock ovunque all'interno. Ho impiegato un po 'di tempo con me e il mio team per realizzarlo da qualche thread online. Quindi questo è solo uno dei motivi per stare attenti a Powermock e probabilmente non usarlo.
Amer,

16

Solo per dare seguito. Aggiungi questa riga al tuo file gradle:

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

Ho provato varie versioni di mockito-core e mockito-all. Nessuno dei due funziona.


1
Per aggiungere a ciò, una cosa che ho osservato è che se si utilizza Powermock insieme a mockito; quindi aggiungere il file del plugin mockmaker in 'src / test / resources / mockito-extensions / org.mockito.plugins.MockMaker' non sarebbe utile per deridere le classi finali. Invece, l'aggiunta di una dipendenza come menzionata da Michael_Zhang sopra risolverebbe il problema del deridere le classi finali. Inoltre, assicurati di utilizzare Mockito 2 invece di Mockito1
dishooom

12

Immagino tu l'abbia fatto finalperché vuoi impedire che altre classi si estendano RainOnTrees. Come suggerisce Effective Java (elemento 15), c'è un altro modo per mantenere una classe vicina per l'estensione senza renderla final:

  1. Rimuovi la finalparola chiave;

  2. Crea il suo costruttore private. Nessuna classe sarà in grado di estenderla perché non sarà in grado di chiamare il supercostruttore;

  3. Crea un metodo factory statico per creare un'istanza della tua classe.

    // 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
        }
    }

Usando questa strategia, sarai in grado di usare Mockito e tenere la tua classe chiusa per l'estensione con un piccolo codice di targa.


1
questo non funziona per i metodi finali che con mockito 2 possono anche essere derisi.
Łukasz Rzeszotarski,

11

Ho avuto lo stesso problema. Poiché la classe che stavo cercando di prendere in giro era una classe semplice, ho semplicemente creato un'istanza di essa e l'ho restituita.


2
Assolutamente, perché deridere una classe semplice? Il derisione è per interazioni "costose": altri servizi, motori, classi di dati ecc.
StripLight

3
Se ne crei un'istanza, non puoi applicare successivamente i metodi Mockito.verify su di essa. L'uso principale di beffe è quello di essere in grado di testare alcuni dei suoi metodi.
riroo,

6

Prova questo:

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

Ha funzionato per me. "SomeMockableType.class" è la classe padre di ciò che vuoi deridere o spiare, e someInstanceThatIsNotMockableOrSpyable è la classe effettiva che vuoi deridere o spiare.

Per maggiori dettagli dai un'occhiata qui


3
Va notato che i delegati sono molto diversi dal deridere la spia nativa. In una spia mockito nativa, "this" nel riferimento all'istanza della spia stessa (perché è la sottoclasse use) Tuttavia, in delegato, "this" sarà l'oggetto reale someInstanceThatIsNotMockableOrSpyable. Non la spia. Pertanto, non è possibile fare Restituire / verificare le funzioni di chiamata automatica.
Dennis C,

1
puoi fare un esempio?
Vishwa Ratna,

5

Un'altra soluzione alternativa, che può essere applicabile in alcuni casi, è quella di creare un'interfaccia implementata da quella classe finale, modificare il codice per utilizzare l'interfaccia anziché la classe concreta e quindi deridere l'interfaccia. Ciò consente di separare il contratto (interfaccia) dall'implementazione (classe finale). Naturalmente, se ciò che vuoi è davvero legare alla classe finale, questo non si applica.


5

In realtà c'è un modo, che uso per spiare. Funzionerebbe per te solo se fossero soddisfatte due condizioni preliminari:

  1. Si utilizza un tipo di DI per iniettare un'istanza della classe finale
  2. La classe finale implementa un'interfaccia

Ricorda l'articolo 16 di Effective Java . È possibile creare un wrapper (non finale) e inoltrare tutte le chiamate all'istanza della classe finale:

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(); }
}

Ora non solo puoi deridere la tua classe finale, ma anche spiarla:

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 e altro ho lo stesso problema e risolto come da questo link

Mock Final Classes and Methods con Mockito come segue

Prima che Mockito possa essere usato per deridere classi e metodi finali, deve essere> configurato.

Dobbiamo aggiungere un file di testo alla directory src / test / resources / mockito-extensions del progetto denominata org.mockito.plugins.MockMaker e aggiungere una singola riga di testo:

mock-maker-inline

Mockito controlla la directory delle estensioni per i file di configurazione quando viene caricato. Questo file consente di deridere i metodi e le classi finali.


4

Risparmio di tempo per le persone che affrontano lo stesso problema (Mockito + Final Class) su Android + Kotlin. Come in Kotlin, le classi sono definitive per impostazione predefinita. Ho trovato una soluzione in uno degli esempi di Google Android con il componente Architecture. Soluzione scelta da qui: https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample

Crea le seguenti annotazioni:

/**
 * 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

Modifica il tuo file gradle. Prendi esempio da qui: 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'
}

Ora puoi annotare qualsiasi classe per renderla aperta ai test:

@OpenForTesting
class RepoRepository 

Funziona bene a livello di app build.gradle ma cosa possiamo fare per farlo a livello di libreria?
Summit T

Puoi elaborare un po '? Di solito, usa il modello di facciata per connetterti alle librerie. E deride queste classi di facciate per testare l'app. In questo modo non abbiamo bisogno di deridere nessuna classe lib.
Ozeetee,

3

Questo può essere fatto se si utilizza Mockito2, con la nuova funzione di incubazione che supporta il derisione di classi e metodi finali.

Punti chiave da notare:
1. Creare un file semplice con il nome "org.mockito.plugins.MockMaker" e posizionarlo in una cartella denominata "mockito-extensions". Questa cartella dovrebbe essere resa disponibile sul percorso di classe.
2. Il contenuto del file creato sopra dovrebbe essere una riga singola come indicato di seguito:
mock-maker-inline

I due passaggi precedenti sono necessari per attivare il meccanismo di estensione mockito e utilizzare questa funzione di attivazione.

Le classi di esempio sono le seguenti: -

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

}

}

Spero che sia d'aiuto.

Articolo completo presente qui beffardo-il-smontabile .


Dovresti includere la risposta qui e non collegarti a un sito esterno. Se la procedura è lunga, è possibile includere una panoramica.
rghome,

assicurati che le annotazioni seguenti vengano utilizzate quando deridi @RunWith (PowerMockRunner.class) @PrepareForTest ({AFinalClass.class})
vCillusion

1
@vCillusion - L'esempio che ho mostrato utilizza solo l'API Mockito2. Usando la funzione opt-in di Mockito2, si possono deridere direttamente le classi finali senza la necessità di usare Powermock.
ksl

2

Sì, lo stesso problema qui, non possiamo prendere in giro un'ultima classe con Mockito. Per essere precisi, Mockito non può deridere / spiare quanto segue:

  • lezioni finali
  • lezioni anonime
  • tipi primitivi

Ma usare una classe wrapper mi sembra un grande prezzo da pagare, quindi prendi invece PowerMockito.


2

Penso che tu debba pensare di più in linea di principio. Invece la classe finale usa invece la sua interfaccia e la sua interfaccia simulata.

Per questo:

 public class RainOnTrees{

   fun startRain():Observable<Boolean>{

        // some code here
   }
}

Inserisci

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

e ti deridi interfaccia:

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

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

    }

1

Si prega di guardare JMockit . Ha una vasta documentazione con molti esempi. Qui hai una soluzione di esempio del tuo problema (per semplificare ho aggiunto il costruttore Seasonsper iniettare RainOnTreesun'istanza derisa ):

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

Le soluzioni fornite da RC e Luigi R. Viggiano insieme sono probabilmente l'idea migliore.

Sebbene Mockito non possa , in base alla progettazione, deridere le classi finali, l'approccio della delega è possibile . Questo ha i suoi vantaggi:

  1. Non sei obbligato a cambiare la tua classe in non definitiva se questo è ciò che la tua API intende in primo luogo (le classi finali hanno i loro vantaggi ).
  2. Stai testando la possibilità di una decorazione attorno alla tua API.

Nel tuo caso di test, inoltri deliberatamente le chiamate al sistema in prova. Quindi, dal design, la tua decorazione no fa nulla.

Quindi il test può anche dimostrare che l'utente può solo decorare l'API invece di estenderla.

Su una nota più soggettiva: preferisco limitare al minimo i framework, motivo per cui JUnit e Mockito sono in genere sufficienti per me. In effetti, limitare questo modo a volte mi costringe anche a fare il refactoring per il bene.


1

Se si sta tentando di eseguire unit-test nella cartella test , la soluzione migliore va bene. Basta seguirlo aggiungendo un'estensione.

Ma se vuoi eseguirlo con una classe relativa ad Android come il contesto o l'attività che si trova nella cartella androidtest , la risposta è per te.


1

Aggiungi queste dipendenze per eseguire mockito correttamente:

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


0

Come altri hanno già affermato, con Mockito non funzionerà immediatamente. Suggerirei di utilizzare la riflessione per impostare i campi specifici sull'oggetto che viene utilizzato dal codice in prova. Se ti ritrovi a fare molto, puoi avvolgere questa funzionalità in una libreria.

Per inciso, se sei l'unico a classificare le classi, smetti di farlo. Ho incontrato questa domanda perché sto lavorando con un'API in cui tutto è stato contrassegnato come definitivo per impedire il mio legittimo bisogno di estensione (derisione) e vorrei che lo sviluppatore non avesse assunto che non avrei mai avuto bisogno di estendere la classe.


1
Le classi di API pubbliche dovrebbero essere aperte per l'estensione. Totalmente d'accordo. Tuttavia, in una base di codice privata, finaldovrebbe essere l'impostazione predefinita.
ErikE

0

Per noi, è stato perché abbiamo escluso il mockito-inline dal test koin. Un modulo gradle in realtà aveva bisogno di questo e per motivi non riusciva solo nelle build di rilascio (build di debug nell'IDE funzionavano) :-P


0

Per la classe finale aggiungi sotto per deridere e chiama statico o non statico.

1- aggiungi questo a livello di classe @SuppressStatucInitializationFor (valore = {nome classe con pacchetto})
2- PowerMockito.mockStatic (classname.class) deriderà la classe
3- quindi usa la tua istruzione quando per restituire un oggetto simulato quando chiama il metodo di questa classe.

Godere


-5

Non ho provato final, ma per il privato, usando la reflection rimuovi il modificatore ha funzionato! ho controllato ulteriormente, non funziona per la finale.


questo non risponde alla domanda posta
Sanjit Kumar Mishra il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.