174 votes

Initialisation des objets fantaisie - MockIto

Il y a plusieurs façons d'initialiser un objet fantaisie en utilisant MockIto. Quel est le meilleur moyen parmi ceux-ci ?

1.

 public class SampleBaseTestCase {

   @Before public void initMocks() {
       MockitoAnnotations.initMocks(this);
   }

@RunWith(MockitoJUnitRunner.class)

mock(XXX.class);

Suggérez-moi s'il y a d'autres moyens meilleurs que ceux-ci...

187voto

gontard Points 5097

Pour l'initialisation des mocks en utilisant le coureur ou le MockitoAnnotations.initMocks sont des solutions strictement équivalentes. D'après la javadoc du MockitoJUnitRunner :

Le runner de JUnit 4.5 initialise les mocks annotés avec Mock, de sorte que l'utilisation explicite de MockitoAnnotations.initMocks(Object) n'est pas nécessaire. Les mocks sont initialisés avant chaque méthode de test.


La première solution (avec le MockitoAnnotations.initMocks ) pourrait être utilisé lorsque vous avez déjà configuré un coureur spécifique ( SpringJUnit4ClassRunner par exemple) sur votre scénario de test.

La deuxième solution (avec le MockitoJUnitRunner ) est le plus classique et mon préféré. Le code est plus simple. L'utilisation d'un runner offre le grand avantage de [validation automatique de l'utilisation du cadre](https://www.javadoc.io/doc/org.mockito/mockito-core/2.2.28/org/mockito/Mockito.html#validateMockitoUsage()) (décrit par David Wallace en cette réponse ).

Les deux solutions permettent de partager les mocks (et les espions) entre les méthodes de test. Couplé avec le @InjectMocks ils permettent d'écrire des tests unitaires très rapidement. Le code de simulation passe-partout est réduit, les tests sont plus faciles à lire. Par exemple :

@RunWith(MockitoJUnitRunner.class)
public class ArticleManagerTest {

    @Mock private ArticleCalculator calculator;
    @Mock(name = "database") private ArticleDatabase dbMock;
    @Spy private UserProvider userProvider = new ConsumerUserProvider();

    @InjectMocks private ArticleManager manager;

    @Test public void shouldDoSomething() {
        manager.initiateArticle();
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        manager.finishArticle();
        verify(database).removeListener(any(ArticleListener.class));
    }
}

Pros : Le code est minimal

Inconvénients : magie noire. IMO, c'est principalement dû à l'annotation @InjectMocks. Avec cette annotation "vous perdez la douleur du code" (voir les excellents commentaires de @Brice )


La troisième solution consiste à créer votre objet fantaisie sur chaque méthode de test. Il permet comme expliqué par @mlk dans sa réponse pour avoir " essai autonome ".

public class ArticleManagerTest {

    @Test public void shouldDoSomething() {
        // given
        ArticleCalculator calculator = mock(ArticleCalculator.class);
        ArticleDatabase database = mock(ArticleDatabase.class);
        UserProvider userProvider = spy(new ConsumerUserProvider());
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.initiateArticle();

        // then 
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        // given
        ArticleCalculator calculator = mock(ArticleCalculator.class);
        ArticleDatabase database = mock(ArticleDatabase.class);
        UserProvider userProvider = spy(new ConsumerUserProvider());
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.finishArticle();

        // then
        verify(database).removeListener(any(ArticleListener.class));
    }
}

Pros : Vous démontrez clairement le fonctionnement de votre API (BDD...).

Contre : il y a plus de code passe-partout. (La création de mocks)


Mon La recommandation est un compromis. Utilisez le @Mock avec l'annotation @RunWith(MockitoJUnitRunner.class) mais n'utilisez pas l'option @InjectMocks :

@RunWith(MockitoJUnitRunner.class)
public class ArticleManagerTest {

    @Mock private ArticleCalculator calculator;
    @Mock private ArticleDatabase database;
    @Spy private UserProvider userProvider = new ConsumerUserProvider();

    @Test public void shouldDoSomething() {
        // given
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.initiateArticle();

        // then 
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        // given
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.finishArticle();

        // then 
        verify(database).removeListener(any(ArticleListener.class));
    }
}

Pros : Vous démontrez clairement le fonctionnement de votre api (Comment mon ArticleManager est instancié). Pas de code passe-partout.

Inconvénients : Le test n'est pas autonome, ce qui réduit la douleur du code.

33voto

Jeff Bowman Points 9712

Il existe maintenant (à partir de la v1.10.7) une quatrième façon d'instancier des mocks, qui consiste à utiliser une méthode JUnit4 regla appelé MockitoRule .

@RunWith(JUnit4.class)   // or a different runner of your choice
public class YourTest
  @Rule public MockitoRule rule = MockitoJUnit.rule();
  @Mock public YourMock yourMock;

  @Test public void yourTestMethod() { /* ... */ }
}

JUnit recherche sous-classes de TestRule annotées avec @Rule et les utilise pour envelopper les déclarations de test que le Runner fournit . Le résultat est que vous pouvez extraire des méthodes @Before, @After, et même des wrappers try...catch dans les règles. Vous pouvez même interagir avec ces méthodes à partir de votre test, de la même manière que les méthodes @Before et @After. ExpectedException fait.

MockitoRule se comporte presque exactement comme MockitoJUnitRunner sauf que vous pouvez utiliser n'importe quel autre exécuteur, tel que Paramétré (qui permet à vos constructeurs de tests de prendre des arguments afin que vos tests puissent être exécutés plusieurs fois), ou le gestionnaire de tests de Robolectric (afin que son chargeur de classes puisse fournir des remplacements Java pour les classes natives d'Android). Cela rend son utilisation strictement plus flexible dans les versions récentes de JUnit et Mockito.

En résumé :

  • Mockito.mock() : Invocation directe sans support d'annotation ni validation d'utilisation.
  • MockitoAnnotations.initMocks(this) : Support des annotations, pas de validation de l'utilisation.
  • MockitoJUnitRunner : Support des annotations et validation de l'utilisation, mais vous devez utiliser ce runner.
  • MockitoRule : Support des annotations et validation de l'utilisation avec n'importe quel runner JUnit.

Voir aussi : Comment fonctionne la @Rule de JUnit ?

31voto

Thanthu Points 1415

1. Utilisation de MockitoAnnotations.openMocks() :

El MockitoAnnotations.initMock() dans Mockito 2 est dépréciée et remplacée par la méthode MockitoAnnotations.openMocks() dans Mockito 3. MockitoAnnotations.openMocks() retourne une instance de AutoClosable qui peut être utilisé pour fermer la ressource après le test. Voici un exemple utilisant MockitoAnnotations.openMocks() .

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

class MyTestClass {

    AutoCloseable openMocks;

    @BeforeEach
    void setUp() {
        openMocks = MockitoAnnotations.openMocks(this);
        // my setup code...
    }

    @Test
    void myTest() {
        // my test code...

    }

    @AfterEach
    void tearDown() throws Exception {
        // my tear down code...
        openMocks.close();
    }

}

2. Utilisation de @ExtendWith(MockitoExtension.class) :

À partir de JUnit5 @RunWith a été supprimé. Voici un exemple utilisant @ExtendWith :

@ExtendWith(MockitoExtension.class)
class MyTestClass {

    @BeforeEach
    void setUp() {
        // my setup code...
    }

    @Test
    void myTest() {
        // my test code...

    }

    @AfterEach
    void tearDown() throws Exception {
        // my tear down code...
    }

}

13voto

fl0w Points 21

Un petit exemple pour JUnit 5 Jupiter, le "RunWith" a été supprimé, vous devez maintenant utiliser les extensions en utilisant l'annotation "@ExtendWith".

@ExtendWith(MockitoExtension.class)
class FooTest {

  @InjectMocks
  ClassUnderTest test = new ClassUnderTest();

  @Spy
  SomeInject bla = new SomeInject();
}

12voto

mlk Points 7270

MockitoAnnotations et le coureur ont été bien discutés ci-dessus, je vais donc ajouter mon grain de sel pour les mal-aimés :

XXX mockedXxx = mock(XXX.class);

Je l'utilise parce que je le trouve un peu plus descriptif et que je préfère (ce n'est pas une interdiction absolue) que les tests unitaires n'utilisent pas de variables membres car j'aime que mes tests soient (autant qu'ils peuvent l'être) autonomes.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X