web-dev-qa-db-fra.com

Comment se moquer d'une dernière classe avec mockito

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 pour Seasons.Java, je veux me moquer de la classe RainOnTrees. Comment puis-je faire cela avec Mockito?

122
buttowski

Mockito final/static classes/methods est possible avec Mockito v2 uniquement.

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

Quelles sont les limites de Mockito

  • Nécessite Java 1.5+

  • Ne peut pas se moquer des classes finales

...

75
user180100

Mockito 2 prend désormais en charge les dernières classes et méthodes!

Mais pour l'instant, c'est une fonctionnalité "d'incubation". Pour l'activer, certaines étapes sont décrites dans Quoi de neuf dans Mockito 2 :

Le moquage des classes et méthodes finales est une fonctionnalité incubating, opt-in. Il utilise une combinaison d'instrumentation d'agent Java et de sous-classement afin de permettre la mockability de ces types. Comme cela fonctionne différemment de notre mécanisme actuel et que celui-ci a différentes limites, et comme nous souhaitons 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.MockMaker contenant 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());

Lors des étapes suivantes, l’équipe apportera un moyen programmatique d’utiliser cette fonctionnalité. Nous identifierons et fournirons une assistance pour tous les scénarios inacceptables. Restez à l'écoute et dites-nous ce que vous pensez de cette fonctionnalité!

135
WindRider

Vous ne pouvez pas vous moquer d'un dernier cours avec Mockito, car vous ne pouvez pas le faire vous-même.

Ce que je fais est de créer un cours non final pour envelopper le cours final et l'utiliser en tant que délégué. Un exemple de ceci est TwitterFactory class, et voici ma classe dérisoire:

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 standard. L'avantage est que vous pouvez ajouter des méthodes pouvant être liées à votre activité applicative (comme getInstance qui prend un utilisateur au lieu d'un accessToken, dans le cas ci-dessus).

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

36
Luigi R. Viggiano

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

25
Gábor Lipták

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

17
BennyP

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

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

J'ai essayé différentes versions de mockito-core et mockito-all. Ni l'un ni l'autre ne travaille.

12
Michael_Zhang

Je suppose que vous avez fait final parce que vous voulez empêcher d'autres classes d'étendre RainOnTrees. Comme Effective Java suggère (point 15), il existe un autre moyen de garder une classe proche d'une extension sans la rendre final:

  1. Supprimez le mot clé final;

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

  3. Créez une méthode fabrique 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 extension avec peu de code passe-partout.

11
Flávio Faria

J'ai eu le même problème. Comme le cours que je tentais de simuler était un cours simple, j'en ai simplement créé une instance et l'ai renvoyé.

6
user1945457

Essayez ceci:

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

Cela a fonctionné 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 voulez simuler ou espionner.

Pour plus de détails jeter un oeil ici

5
sakis kaliakoudas

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 à simuler 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.

4
andresp

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 le point 16 de Effective Java . Vous pouvez créer un wrapper (non final) et transférer tous les appels vers 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 cours final, 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
xuesheng

Oui même problème ici, nous ne pouvons pas nous moquer d'une dernière classe avec Mockito. Pour être précis, Mockito ne peut pas se moquer/espionner après:

  • classes finales
  • cours anonymes
  • types primitifs

Mais utiliser une classe wrapper me semble un gros prix à payer, alors procurez-vous PowerMockito.

2
Yu Chen

Cela peut être fait si vous utilisez Mockito2, avec la nouvelle fonctionnalité d’incubation qui prend en charge les moqueries 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 devrait être mis à disposition sur le classpath.
2. Le contenu du fichier créé ci-dessus devrait ê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 fonctionnalité.

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/ se moquant-l'inoubliable .

2
ksl

Gain de temps pour les personnes confrontées au même problème (Mockito + Final Class) sur Android + Kotlin. Comme en 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 à partir d'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 de classement. Prenons un exemple 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 aux tests:

@OpenForTesting
class RepoRepository 
2
Ozeetee

S'il vous plaît regardez JMockit . Il a une documentation complète avec beaucoup d'exemples. Ici, vous avez un exemple de solution à votre problème (pour simplifier, j'ai ajouté le constructeur à Seasons pour injecter une instance RainOnTrees 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
Marcin

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

En passant, si vous êtes celui qui marque les classes, arrêtez de le faire. J'ai rencontré cette question parce que je travaillais avec une API où tout était marqué comme final pour éviter mon besoin légitime d'extension (moqueur), et j'aimerais que le développeur n'ait pas supposé que je n'aurais jamais besoin d'étendre la classe.

0
Tom N.

Si vous essayez d'exécuter le test unitaire sous le dossier test, la solution proposée est la meilleure. Il suffit de suivre en ajoutant une extension.

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

0
Allen Wang

Pour nous, c'est parce que nous avons exclu mockito-inline de koin-test. Un module de classe en avait réellement besoin et, pour cette raison, n’avait échoué que sur les versions de version (les versions de débogage dans la IDE travaillaient) :-P

0
kenyee

Je pense que vous devez penser plus en principe. Au lieu de cela, la classe finale utilise son interface et une interface fictive à la place.

Pour ça:

 public class RainOnTrees{

   fun startRain():Observable<Boolean>{

        // some code here
   }
}

ajouter

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

et vous moquez de l'interface:

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

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

    }
0
Serg Burlaka