web-dev-qa-db-fra.com

Initialiser des objets fantaisie - MockIto

Il existe plusieurs façons d'initialiser un objet fictif à l'aide de MockIto . Quelle est la meilleure façon parmi celles-ci?

1.

 public class SampleBaseTestCase {

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

2.

@RunWith(MockitoJUnitRunner.class)

[EDIT] 3.

mock(XXX.class);

suggère-moi s'il existe d'autres moyens meilleurs que ceux-là ... 

92
VinayVeluri

Pour l'initialisation de mocks , l'utilisation du coureur ou du _MockitoAnnotations.initMocks_ sont des solutions strictement équivalentes. Du javadoc de la MockitoJUnitRunner :

JUnit 4.5 runner initializes mocks annotated with Mock, so that explicit usage of MockitoAnnotations.initMocks(Object) is not necessary. Mocks are initialized before each test method.


La première solution (avec le _MockitoAnnotations.initMocks_) peut être utilisée si vous avez déjà configuré un exécuteur spécifique (_SpringJUnit4ClassRunner_ par exemple) sur votre scénario de test.

La deuxième solution (avec le MockitoJUnitRunner) est la plus classique et la plus préférée. Le code est plus simple. L'utilisation d'un coureur offre le grand avantage de validation automatique de l'utilisation du framework (décrit par @ David Wallace = dans cette réponse ).

Les deux solutions permettent de partager les simulacres (et les espions) entre les méthodes de test. Couplés avec le @InjectMocks , ils permettent d'écrire des tests unitaires très rapidement. Le code de moquette 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));
    }
}
_

Le code est minime

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


La troisième solution consiste à créer votre maquette sur chaque méthode de test. Il permet, comme expliqué par @ mlk dans sa réponse, d'avoir " un test 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));
    }
}
_

Avantages: vous démontrez clairement le fonctionnement de votre API (BDD ...)

Inconvénients: il y a plus de code passe-partout. (La création moquée)


Ma recommandation est un compromis. Utilisez l'annotation _@Mock_ avec la @RunWith(MockitoJUnitRunner.class), mais n'utilisez pas la _@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));
    }
}
_

Avantages: vous démontrez clairement comment votre API fonctionne (comment mon ArticleManager est instancié). Pas de code passe-partout.

Inconvénients: le test n'est pas autonome, moins de douleur du code

127
gontard

Il existe maintenant (à partir de la v1.10.7) une quatrième méthode pour instancier des simulacres, qui utilise un JUnit4 rule 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 les sous-classes de TestRule annotées avec @Rule et les utilise pour wrap les instructions de test fournies par le Runner. Le résultat de ceci est que vous pouvez extraire les méthodes @Before, les méthodes @After et même essayer ... attraper des wrappers dans des règles. Vous pouvez même interagir avec ceux-ci à partir de votre test, de la même manière que ExpectedException .

MockitoRule se comporte presque exactement comme MockitoJUnitRunner, sauf que vous pouvez utiliser tout autre coureur, tel que Parameterized (qui permet à vos constructeurs de test de prendre des arguments pour pouvoir exécuter vos tests plusieurs fois) ou au testeur de Robolectric (son chargeur de classe peut donc fournir des remplacements Java pour les classes natives Android). Cela le rend strictement plus flexible dans les versions récentes de JUnit et Mockito.

En résumé:

  • Mockito.mock(): Invocation directe sans prise en charge d'annotation ni validation de l'utilisation.
  • MockitoAnnotations.initMocks(this): Prise en charge des annotations, aucune validation d'utilisation.
  • MockitoJUnitRunner: support d'annotation et validation de l'utilisation, mais vous devez utiliser ce coureur.
  • MockitoRule: support des annotations et validation de l'utilisation avec tout coureur JUnit.

Voir aussi: Comment fonctionne JUnit @Rule?

22
Jeff Bowman

Il y a une bonne façon de faire cela.

  • Si c'est un test unitaire, vous pouvez le faire:

    @RunWith(MockitoJUnitRunner.class)
    public class MyUnitTest {
    
        @Mock
        private MyFirstMock myFirstMock;
    
        @Mock
        private MySecondMock mySecondMock;
    
        @Spy
        private MySpiedClass mySpiedClass = new MySpiedClass();
    
        // It's gonna inject the 2 mocks and the spied object per reflection to this object
        // The Java doc of @InjectMocks explains it really well how and when it does the injection
        @InjectMocks
        private MyClassToTest myClassToTest;
    
        @Test
        public void testSomething() {
        }
    }
    
  • EDIT: s’il s’agit d’un test d’intégration, vous pouvez le faire (cela n’est pas destiné à être utilisé de cette façon avec Spring. Il vous suffit de montrer que vous pouvez initialiser des simulacres avec différents Runners):

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("aplicationContext.xml")
    public class MyIntegrationTest {
    
        @Mock
        private MyFirstMock myFirstMock;
    
        @Mock
        private MySecondMock mySecondMock;
    
        @Spy
        private MySpiedClass mySpiedClass = new MySpiedClass();
    
        // It's gonna inject the 2 mocks and the spied object per reflection to this object
        // The Java doc of @InjectMocks explains it really well how and when it does the injection
        @InjectMocks
        private MyClassToTest myClassToTest;
    
        @Before
        public void setUp() throws Exception {
              MockitoAnnotations.initMocks(this);
        }
    
        @Test
        public void testSomething() {
        }
    }
    
9
emd

MockitoAnnotations et le coureur ont été bien discutés ci-dessus, alors je vais donner ma place pour ceux qui ne sont pas aimés:

XXX mockedXxx = mock(XXX.class);

J'utilise ceci parce que je le trouve un peu plus descriptif et que je préfère (ne pas bannir à droite) les tests unitaires ne pas utiliser de variables membres, car j'aime que mes tests soient (autant que possible) autonomes.

8