Comment se moquer d'un dernier cours avec mockito


218

J'ai un dernier cours, quelque chose comme ça:

public final class RainOnTrees{

   public void startRain(){

        // some code here
   }
}

J'utilise cette classe dans une autre classe comme celle-ci:

public class Seasons{

   RainOnTrees rain = new RainOnTrees();

   public void findSeasonAndRain(){

        rain.startRain();

    }
}

et dans ma classe de test JUnit car Seasons.javaje veux me moquer de la RainOnTreesclasse. Comment puis-je faire cela avec Mockito?


9
Mockito ne le permet pas, contrairement à PowerMock.
fge

1
Depuis Mockito 2.x, Mockito prend désormais en charge la simulation des classes et méthodes finales.
Kent

Duplication possible de la classe finale
eliasah

Réponses:


156

La simulation des classes / méthodes finales / statiques est possible uniquement avec Mockito v2.

ajoutez ceci dans votre fichier gradle:

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

Ce n'est pas possible avec Mockito v1, à partir de la FAQ Mockito :

Quelles sont les limites de Mockito

  • Nécessite Java 1.5+

  • Impossible de se moquer des classes finales

...


2
Cela n'a pas fonctionné pour moi à Scala (avec modifications sbt).
micseydel

2
Ce n'était pas suffisant pour moi. J'ai également dû créer src / test / resources / mockito-extensions / org.mockito.plugins.MockMaker avec "mock-maker-inline" dedans selon baeldung.com/mockito-final
micseydel

205

Mockito 2 prend désormais en charge les classes et méthodes finales !

Mais pour l'instant, c'est une fonction "d'incubation". L'activation nécessite certaines étapes décrites dans Quoi de neuf dans Mockito 2 :

La moquerie des classes et des méthodes finales est une fonction d' incubation opt-in. Il utilise une combinaison d'instrumentation d'agent Java et de sous-classement afin d'activer la moquabilité de ces types. Comme cela fonctionne différemment de notre mécanisme actuel et que celui-ci a des limites différentes et que nous voulons recueillir l'expérience et les commentaires des utilisateurs, cette fonctionnalité devait être explicitement activée pour être disponible; cela peut être fait via le mécanisme d'extension mockito en créant le fichier src/test/resources/mockito-extensions/org.mockito.plugins.MockMakercontenant une seule ligne:

mock-maker-inline

Après avoir créé ce fichier, Mockito utilisera automatiquement ce nouveau moteur et on peut faire:

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

Dans les étapes suivantes, l'équipe apportera une manière programmatique d'utiliser cette fonctionnalité. Nous identifierons et fournirons un support pour tous les scénarios non modifiables. Restez à l'écoute et faites-nous savoir ce que vous pensez de cette fonctionnalité!


14
Je reçois toujours une erreur: Impossible de se moquer / espionner la classe android.content.ComponentName Mockito ne peut pas se moquer / espionner parce que: - classe finale
IgorGanapolsky

3
Assurez-vous de placer le org.mockito.plugins.MockMakerfichier dans le bon dossier.
WindRider

7
Je reçois également l'erreur même après avoir suivi ce qui précède: Mockito ne peut pas se moquer / espionner parce que: - classe finale
rcde0

8
@vCillusion, cette réponse n'est en aucun cas liée à PowerMock.
Ligne

6
J'ai suivi ces instructions mais je ne pouvais toujours pas faire ce travail, quelqu'un devait-il faire autre chose?
Franco

43

Vous ne pouvez pas vous moquer d'une classe finale avec Mockito, car vous ne pouvez pas le faire vous-même.

Ce que je fais, c'est de créer une classe non finale pour envelopper la classe finale et l'utiliser comme délégué. Un exemple de ceci est la TwitterFactoryclasse, et voici ma classe moquable:

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

L'inconvénient est qu'il y a beaucoup de code passe-partout; l'avantage est que vous pouvez ajouter certaines méthodes qui peuvent se rapporter à votre activité d'application (comme getInstance qui prend un utilisateur au lieu d'un accessToken, dans le cas ci-dessus).

Dans votre cas, je créerais une RainOnTreesclasse non finale qui déléguerait à la classe finale. Ou, si vous pouvez le rendre non final, ce serait mieux.


6
+1. Si vous le souhaitez, vous pouvez utiliser quelque chose comme Lombok @Delegatepour gérer une grande partie du passe-partout.
ruakh

2
@luigi pouvez-vous ajouter un extrait de code pour Junit à titre d'exemple. J'ai essayé de créer Wrapper pour ma classe finale, mais je n'ai pas pu déterminer comment le tester.
Incroyable

31

ajoutez ceci dans votre fichier gradle:

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

ceci est une configuration pour faire fonctionner mockito avec les classes finales


1
Devrait probablement utiliser "testImplementation" maintenant au lieu de "testCompile". Gradle n'aime plus "testCompile".
jwehrle

grand commentaire, merci! modifié pour tester la mise en œuvre. commentaire d'origine: testCompile 'org.mockito: mockito-inline: 2.13.0'
BennyP

2
Il en résulte une erreur lors de l'exécution sur 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

Fonctionne
correctement

23

Utilisez Powermock. Ce lien montre comment le faire: https://github.com/jayway/powermock/wiki/MockFinal


30
Je pense que PowerMock est comme l'un de ces médicaments qui ne devraient sortir que sur une base de "prescription". Dans le sens de: il faut dire très clairement que PowerMock a beaucoup de problèmes; et que son utilisation est comme le dernier recours ultime; et doit être évité autant que possible.
GhostCat

1
Pourquoi dites vous cela?
PragmaticProgrammer

J'utilisais Powermockpour me moquer des classes finales et des méthodes statiques pour augmenter ma couverture qui a été officiellement vérifiée Sonarqube. La couverture était de 0% depuis SonarQube, pour quelque raison que ce soit, elle ne reconnaît pas les classes qui utilisent Powermock n'importe où à l'intérieur. J'ai pris moi et mon équipe un certain temps pour le réaliser à partir d'un fil en ligne. Ce n'est donc qu'une des raisons de faire attention à Powermock et de ne pas l'utiliser.
amer

16

Juste pour faire un suivi. Veuillez ajouter cette ligne à votre fichier de notes:

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

J'ai essayé différentes versions de mockito-core et mockito-all. Aucun d'eux ne fonctionne.


1
Pour ajouter à cela, une chose que j'ai observée est que si vous utilisez Powermock avec mockito; puis ajouter le fichier du plugin mockmaker dans 'src / test / resources / mockito-extensions / org.mockito.plugins.MockMaker' ne serait pas utile pour se moquer des classes finales. Au lieu de cela, l'ajout d'une dépendance comme mentionné par Michael_Zhang ci-dessus résoudrait le problème des classes finales moqueuses. Assurez-vous également que vous utilisez Mockito 2 au lieu de Mockito1
dishooom

12

Je suppose que vous l'avez fait finalparce que vous voulez empêcher les autres classes de s'étendre RainOnTrees. Comme le suggère Effective Java (point 15), il existe une autre façon de garder une classe proche pour l'extension sans la faire final:

  1. Supprimez le finalmot - clé;

  2. Faites son constructeur private. Aucune classe ne pourra l'étendre car elle ne pourra pas appeler le superconstructeur;

  3. Créez une méthode d'usine statique pour instancier votre 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
        }
    }

En utilisant cette stratégie, vous pourrez utiliser Mockito et garder votre classe fermée pour l'extension avec peu de code standard.


1
cela ne fonctionne pas pour les méthodes finales qui avec mockito 2 peuvent également être moquées.
Łukasz Rzeszotarski

11

J'ai eu le même problème. Étant donné que la classe que j'essayais de simuler était une classe simple, j'en ai simplement créé une instance et je l'ai renvoyée.


2
Absolument, pourquoi se moquer d'une classe simple? La simulation est pour les interactions «coûteuses»: autres services, moteurs, classes de données, etc.
StripLight

3
Si vous créez une instance de cela, vous ne pouvez pas lui appliquer les méthodes Mockito.verify par la suite. L'utilisation principale des simulateurs est de pouvoir tester certaines de ses méthodes.
riroo

6

Essayez ceci:

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

Ça a marché pour moi. "SomeMockableType.class" est la classe parente de ce que vous voulez simuler ou espionner, et someInstanceThatIsNotMockableOrSpyable est la classe réelle que vous souhaitez simuler ou espionner.

Pour plus de détails jetez un œil ici


3
Il convient de noter que les délégués sont très différents des moqueries d'espionnage natives. Dans un espion mockito natif, "ceci" dans la référence d'instance à l'espion lui-même (car il s'agit d'une sous-classe). Cependant, dans le délégué, "ceci" sera le véritable objet someInstanceThatIsNotMockableOrSpyable. Pas l'espion. Ainsi, il n'y a aucun moyen de faireRetourner / vérifier les fonctions auto-appelantes.
Dennis C

1
pouvez-vous donner un exemple?
Vishwa Ratna

5

Une autre solution de contournement, qui peut s'appliquer dans certains cas, consiste à créer une interface implémentée par cette classe finale, à modifier le code pour utiliser l'interface au lieu de la classe concrète, puis à se moquer de l'interface. Cela vous permet de séparer le contrat (interface) de l'implémentation (classe finale). Bien sûr, si vous voulez vraiment vous lier à la classe finale, cela ne s'appliquera pas.


5

En fait, il y a un moyen que j'utilise pour espionner. Cela ne fonctionnerait pour vous que si deux conditions préalables sont remplies:

  1. Vous utilisez une sorte de DI pour injecter une instance de classe finale
  2. La classe finale implémente une interface

Veuillez rappeler l'article 16 de Java efficace . Vous pouvez créer un wrapper (non final) et transférer tous les appels à l'instance de la 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(); }
}

Maintenant, vous pouvez non seulement vous moquer de votre classe finale, mais aussi l'espionner:

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

Dans Mockito 3 et plus, j'ai le même problème et je l'ai résolu à partir de ce lien

Mock classes et méthodes finales avec Mockito comme suit

Avant que Mockito puisse être utilisé pour se moquer des classes et méthodes finales, il doit être> configuré.

Nous devons ajouter un fichier texte au répertoire src / test / resources / mockito-extensions du projet nommé org.mockito.plugins.MockMaker et ajouter une seule ligne de texte:

mock-maker-inline

Mockito vérifie le répertoire d'extensions pour les fichiers de configuration lors de son chargement. Ce fichier permet de se moquer des méthodes et classes finales.


4

Gain de temps pour les personnes confrontées au même problème (Mockito + Final Class) sur Android + Kotlin. Comme dans Kotlin, les classes sont finales par défaut. J'ai trouvé une solution dans l'un des exemples Google Android avec le composant Architecture. Solution choisie ici: https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample

Créez les annotations suivantes:

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

Modifiez votre fichier gradle. Prenons l'exemple d'ici: 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'
}

Vous pouvez maintenant annoter n'importe quelle classe pour la rendre ouverte aux tests:

@OpenForTesting
class RepoRepository 

Cela fonctionne bien au niveau de l'application build.gradle, mais que pouvons-nous faire pour obtenir cela au niveau de la bibliothèque?
Sumit T

Pouvez-vous élaborer un peu? Habituellement, utilisez le motif de façade pour vous connecter aux bibliothèques. Et simulez ces classes de façade pour tester l'application. De cette façon, nous n'avons pas besoin de nous moquer des classes lib.
Ozeetee

3

Cela peut être fait si vous utilisez Mockito2, avec la nouvelle fonction d'incubation qui prend en charge la simulation des classes et méthodes finales.

Points clés à noter:
1. Créez un fichier simple avec le nom «org.mockito.plugins.MockMaker» et placez-le dans un dossier nommé «mockito-extensions». Ce dossier doit être disponible sur le chemin de classe.
2. Le contenu du fichier créé ci-dessus doit être une seule ligne comme indiqué ci - dessous:
mock-maker-inline

Les deux étapes ci-dessus sont nécessaires pour activer le mécanisme d'extension mockito et utiliser cette fonction d'opt-in.

Les exemples de classes sont les suivants: -

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

}

}

J'espère que ça aide.

Article complet présent ici mocking-the-unmockable .


Vous devez inclure la réponse ici et ne pas créer de lien vers un site externe. Si la procédure est longue, vous pouvez inclure un aperçu.
rghome

veuillez vous assurer que les annotations ci-dessous sont utilisées lors de la moquerie de @RunWith (PowerMockRunner.class) @PrepareForTest ({AFinalClass.class})
vCillusion

1
@vCillusion - L'exemple que j'ai montré utilise uniquement l'API Mockito2. En utilisant la fonctionnalité opt-in de Mockito2, on peut se moquer directement des classes finales sans avoir besoin d'utiliser Powermock.
ksl

2

Oui même problème ici, on ne peut pas se moquer d'un dernier cours avec Mockito. Pour être précis, Mockito ne peut pas se moquer / espionner ce qui suit:

  • classes finales
  • classes anonymes
  • types primitifs

Mais utiliser une classe wrapper me semble un gros prix à payer, alors achetez plutôt PowerMockito.


2

Je pense que vous devez réfléchir davantage en principe. Au lieu de cela, la classe finale utilise plutôt son interface et son interface factice.

Pour ça:

 public class RainOnTrees{

   fun startRain():Observable<Boolean>{

        // some code here
   }
}

ajouter

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

et vous moquer de votre interface:

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

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

    }

1

Veuillez regarder JMockit . Il a une documentation complète avec beaucoup d'exemples. Voici un exemple de solution à votre problème (pour simplifier, j'ai ajouté un constructeur Seasonspour injecter une RainOnTreesinstance simulée ):

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

Les solutions fournies conjointement par RC et Luigi R. Viggiano sont peut-être la meilleure idée.

Bien que Mockito ne puisse pas , par conception, se moquer des classes finales, l'approche de délégation est possible . Cela a ses avantages:

  1. Vous n'êtes pas obligé de changer votre classe en non-final si c'est ce que votre API prévoit en premier lieu (les classes finales ont leurs avantages ).
  2. Vous testez la possibilité d'une décoration autour de votre API.

Dans votre scénario de test, vous transférez délibérément les appels vers le système testé. Par conséquent, par la conception, votre décoration ne pas faire quoi que ce soit.

Par conséquent, votre test peut également démontrer que l'utilisateur peut uniquement décorer l'API au lieu de l'étendre.

Sur une note plus subjective: je préfère garder les frameworks au minimum, c'est pourquoi JUnit et Mockito me suffisent généralement. En fait, restreindre cette voie m'oblige parfois à refactoriser pour de bon aussi.


1

Si vous essayez d'exécuter le test unitaire sous le test dossier de , la meilleure solution est très bien. Suivez-le en ajoutant une extension.

Mais si vous voulez l'exécuter avec une classe liée à Android comme le contexte ou l'activité qui se trouve dans le dossier androidtest , la réponse est pour vous.


1

Ajoutez ces dépendances pour exécuter mockito avec succès:

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


0

Comme d'autres l'ont dit, cela ne fonctionnera pas avec Mockito. Je suggère d'utiliser la réflexion pour définir les champs spécifiques de l'objet utilisé par le code testé. Si vous vous retrouvez souvent à faire cela, vous pouvez envelopper cette fonctionnalité dans une bibliothèque.

Soit dit en passant, si vous êtes le seul à marquer les classes finales, arrêtez de le faire. J'ai rencontré cette question parce que je travaille avec une API où tout a été marqué comme final pour éviter mon besoin légitime d'extension (moqueuse), et je souhaite que le développeur n'ait pas supposé que je n'aurais jamais besoin d'étendre la classe.


1
Les classes d'API publiques doivent être ouvertes pour l'extension. Tout à fait d'accord. Cependant, dans une base de code privée, finaldevrait être la valeur par défaut.
ErikE

0

Pour nous, c'est parce que nous avons exclu le mockito-inline du koin-test. Un module gradle en avait réellement besoin et, pour des raisons, n'a échoué que sur les versions (les versions de débogage dans l'EDI ont fonctionné) :-P


0

Pour la classe finale, ajoutez ci-dessous pour simuler et appeler statique ou non statique.

1- ajoutez ceci au niveau de la classe @SuppressStatucInitializationFor (valeur = {nom de classe avec package})
2- PowerMockito.mockStatic (classname.class) se moquera de la classe
3- puis utilisez votre instruction when pour renvoyer l'objet simulé lors de l'appel de la méthode de cette classe.

Prendre plaisir


-5

N'a pas essayé la finale, mais pour le privé, en utilisant la réflexion, supprimez le modificateur travaillé! ont vérifié plus loin, cela ne fonctionne pas pour la finale.


cela ne répond pas à la question posée
Sanjit Kumar Mishra
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.