Comment se moquer d'un cours final avec mockito

j'ai une dernière classe, 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 pour Seasons.java je veux me moquer de la classe RainOnTrees . Comment je peux faire ça avec Mockito?

109
demandé sur LisaMM 2013-01-12 15:23:01

18 réponses

moqueries des classes/méthodes finales / statiques est possible avec Mockito v2 seulement.

ce n'est pas possible avec Mockito v1, de la Mockito FAQ :

quelles sont les limites de Mockito

  • Besoins de java 1.5+

  • ne peut pas simuler les classes finales

...

67
répondu Community 2017-05-23 11:47:31

Mockito 2 supporte maintenant les classes et méthodes !

Mais pour l'instant c'est un "incubation". Il nécessite quelques étapes pour l'activer qui sont décrites dans Quoi de neuf dans Mockito 2 :

se moquant de finale de classes et de méthodes est un incubation , l'opt-in fonctionnalité. Il utilise une combinaison D'instrumentation Java agent et de subclassage afin de permet 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é a dû être explicitement activée pour être disponible ; il peut être fait via le mécanisme d'extension mockito en créant le fichier src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker contenant une seule ligne:

mock-maker-inline

après que vous ayez 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 subséquentes, l'équipe apportera une programmatiques façon d'utiliser cette fonctionnalité. Nous identifierons et soutiendrons tous les scénarios impossibles à gérer. Restez à l'écoute et s'il vous plaît laissez-nous savoir ce que vous pensez de cette fonctionnalité!

101
répondu WindRider 2017-07-14 06:48:55

vous ne pouvez pas vous moquer d'un cours final avec Mockito, comme vous ne pouvez pas le faire par vous-même.

ce que je fais, c'est créer une classe non finale pour envelopper la classe finale et l'utiliser comme délégué. Un exemple de cela est TwitterFactory classe, et c'est ma classe mockable:

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, c'est qu'il y a beaucoup de code réutilisable; l'avantage est que vous pouvez ajouter quelques méthodes qui peuvent se rapporter à votre application d'entreprise (comme le getInstance qui prend un utilisateur au lieu d'un accessstoken, dans le cas ci-dessus).

dans votre cas, je créerais une classe non-finale RainOnTrees qui délègue à la classe finale. Ou, si vous pouvez le faire non définitive, elle serait mieux.

34
répondu Luigi R. Viggiano 2013-01-12 11:45:34

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

25
répondu Gábor Lipták 2016-03-29 09:08:36

pour faire un suivi. Veuillez ajouter cette ligne à votre fichier gradle:

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

j'ai essayé différentes versions de mockito-core et mockito. Ni de leur travail.

11
répondu Michael_Zhang 2017-07-05 22:42:01

ajoutez ceci dans votre fichier gradle:

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

c'est une configuration pour faire fonctionner mockito avec les classes finales

11
répondu BennyP 2018-02-22 08:47:54

je suppose que vous avez fait final parce que vous voulez empêcher d'autres classes de prolonger RainOnTrees . Comme Java efficace le suggère (item 15), Il y a une autre façon de garder une classe proche pour extension sans la rendre final :

  1. supprimer le final mot-clé;

  2. faire son constructeur private . Aucune classe ne pourra l'étendre car il ne pourra pas appeler le constructeur super ;

  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 serez en mesure d'utiliser Mockito et garder votre classe fermée pour l'extension avec peu de code boilerplate.

8
répondu Flávio Faria 2016-07-15 11:56:02

j'ai eu le même problème. Puisque la classe que j'essayais de moquer était une classe simple, j'en ai simplement créé une instance et je l'ai rendue.

6
répondu user1945457 2013-12-10 11:35:22

essayez ceci:

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

ça a marché pour moi. "SomeMockableType.class" est la classe de parent de ce que vous voulez moquer ou espionner, et somethinstancethatisnotmockableorspyable est la classe réelle que vous voulez moquer ou espionner.

pour plus de détails, voir ici

5
répondu sakis kaliakoudas 2014-11-20 12:17:34

une autre solution, qui peut s'appliquer dans certains cas, est de créer une interface qui est implémentée par cette classe finale, de changer le code pour utiliser l'interface à la place de la classe concrete et ensuite de 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 de se lier à la classe finale, cela ne s'applique pas.

4
répondu andresp 2015-02-02 15:46:39

en fait, il y a un moyen, que j'utilise pour espionner. Cela ne marcherait 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. Dernier de la classe implémente une interface

Veuillez rappeler l'Article 16 de Effective Java . Vous pouvez créer un wrapper (pas 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, non seulement vous pouvez 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();
4
répondu xuesheng 2017-05-16 12:17:52

oui même problème ici, nous ne pouvons pas nous moquer d'un cours final avec Mockito. Pour être précis, Mockito ne peut pas se moquer / espionner comme suit:

  • classes finales
  • classes anonymes
  • types primitifs

mais l'utilisation d'une classe d'emballage me semble un grand prix à payer, donc obtenir PowerMockito à la place.

2
répondu Yu Chen 2016-06-17 21:48:57

cela peut être fait si vous utilisez Mockito2, avec la nouvelle fonctionnalité d'incubation qui supporte la moquerie des classes finales et des méthodes.

points clés de la note:

1. Créez un fichier simple avec le nom "org.mockito.greffon.MockMaker" et le placer dans un dossier nommé "mockito-extensions". Ce dossier devrait être disponible sur classpath.

2. Le contenu du fichier créé ci-dessus doivent ê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 opt-in.

Les classes D'échantillon

sont les suivantes: -

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 aidera.

article Complet ici présents se moquant de-la-unmockable .

2
répondu ksl 2017-02-05 19:44:05

s'il vous Plaît regardez JMockit . Il a une documentation complète avec beaucoup d'exemples. Voici un exemple de solution de votre problème (pour simplifier j'ai ajouté le constructeur à Seasons pour injecter l'instance RainOnTrees moqué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
répondu Marcin 2014-10-31 20:46:41

Solutions fournies par RC et Luigi R. Viggiano ensemble est peut-être la meilleure idée.

bien que Mockito ne puisse pas , par conception, des classes finales simulées, 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 cas de test, vous transférez délibérément les appels au système sous test. Par conséquent, par conception, votre décoration ne pas faire quoi que ce soit.

par conséquent, vous pouvez également tester peut démontrer que l'utilisateur ne peut décorer L'API au lieu de l'étendre.

On une note plus subjective: Je préfère garder les cadres au minimum, C'est pourquoi JUnit et Mockito sont généralement suffisants pour moi. En effet, la restriction de cette façon, parfois, m'oblige à refactoriser pour de bon.

1
répondu Neel 2015-12-08 22:04:31

comme d'autres l'ont dit, cela ne marchera pas avec Mockito. Je suggère d'utiliser la réflexion pour définir les champs spécifiques sur l'objet qui est utilisé par le code à l'essai. Si vous vous retrouver à faire beaucoup, vous pouvez envelopper cette fonction dans une bibliothèque.

en passant, si c'est vous qui marquez les classes finales, arrêtez de le faire. Je suis tombé sur cette question parce que je travaille avec une API où tout a été marqué final pour empêcher mon besoin légitime d'extension (moquerie), et je souhaite que le développeur n'avait pas supposé que je n'aurais jamais besoin d'étendre la classe.

0
répondu Tom N. 2017-01-05 13:54:49

gain de temps pour les personnes qui font face 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 un des échantillons Google Android avec un composant D'Architecture. Solution choisie ici: https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample

créer 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

modifier votre dossier gradle. Prenez 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'
}

maintenant vous pouvez annoter n'importe quelle classe pour la rendre ouverte pour les tests:

@OpenForTesting
class RepoRepository 
0
répondu Ozeetee 2018-05-21 22:47:39

N'a pas essayé finale, mais pour privé, en utilisant la réflexion supprimer le modificateur travaillé ! avoir vérifié encore, il ne fonctionne pas pour la finale.

-4
répondu lwpro2 2018-07-11 03:47:19