78 votes

Mockito, JUnit et Spring

Je n'ai commencé à connaître Mockito qu'aujourd'hui. J'ai écrit quelques tests simples (avec JUnit, voir ci-dessous), mais je n'arrive pas à comprendre comment utiliser l'objet mock dans les managed beans de Spring. Qu'est-ce que meilleures pratiques pour travailler avec Spring. Comment injecter une dépendance fantaisie dans mon bean ?

Vous pouvez sauter cette étape jusqu'à retour à ma question .

Tout d'abord, ce que j'ai appris. C'est un très bon article Les mocks ne sont pas des stubs qui explique les bases (les vérifications de Mock vérification du comportement pas vérification des états ). Il y a ensuite un bon exemple ici Mockito et ici Moquerie plus facile avec mockito . Nous avons expliqué que les objets fantaisie de Mockito sont à la fois simuler y talon .

Ici Mockito et ici Matchers vous trouverez d'autres exemples.

Ce test

@Test
public void testReal(){
    List<String> mockedList = mock(List.class);
     //stubbing
     //when(mockedList.get(0)).thenReturn("first");

    mockedList.get(anyInt());
    OngoingStubbing<String> stub= when(null);
    stub.thenReturn("first");

    //String res = mockedList.get(0);
                //System.out.println(res);

     //you can also verify using argument matcher
     //verify(mockedList).get(anyInt());

    verify(mockedList);
    mockedList.get(anyInt());
}

fonctionne très bien.

Revenons à ma question. Ici Injection de mocks Mockito dans un bean Spring quelqu'un essaie d'utiliser Springs ReflectionTestUtils.setField() mais alors ici Tests d'intégration de Spring, création de Mock Objects nous avons une recommandation pour changement Le contexte du printemps.

Je n'ai pas vraiment compris les deux derniers liens... Quelqu'un peut-il m'expliquer quel est le problème de Spring avec Mockito ? Quel est le problème de cette solution ?

@InjectMocks
private MyTestObject testObject

@Mock
private MyDependentObject mockedObject

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

https://stackoverflow.com/a/8742745/1137529

EDIT : Je n'ai pas été très clair. Je vais fournir 3 exemples de code pour me clarifier : Supposons que nous ayons un haricot HelloWorld avec une méthode printHello() et le haricot HelloFacade avec la méthode sayHello qui transmet les appels à la méthode HelloWorld printHello() .

Le premier exemple utilise le contexte de Spring et sans programme d'exécution personnalisé, en utilisant ReflectionTestUtils pour l'injection de dépendances (DI) :

public class Hello1Test  {
private ApplicationContext ctx;

@Before
public void setUp() {
    MockitoAnnotations.initMocks(this);
    this.ctx = new ClassPathXmlApplicationContext("META-INF/spring/ServicesImplContext.xml");
}

@Test
public void testHelloFacade() {
    HelloFacade obj = (HelloFacade) ctx.getBean(HelloFacadeImpl.class);
    HelloWorld mock = mock(HelloWorld.class);
    doNothing().when(mock).printHello();

    ReflectionTestUtils.setField(obj, "hello", mock);
    obj.sayHello();

    verify(mock, times(1)).printHello();
}

}

Comme @Noam l'a fait remarquer, il existe un moyen de l'exécuter sans faire appel explicitement à MockitoAnnotations.initMocks(this); . J'abandonnerai également l'utilisation du contexte de Spring sur cet exemple.

@RunWith(MockitoJUnitRunner.class)
public class Hello1aTest {

@InjectMocks
private HelloFacade obj =  new HelloFacadeImpl();

@Mock
private HelloWorld mock;

@Test
public void testHelloFacade() {
    doNothing().when(mock).printHello();
    obj.sayHello();
}

}

Une autre façon de procéder

public class Hello1aTest {

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

@InjectMocks
private HelloFacadeImpl obj;

@Mock
private HelloWorld mock;

@Test
public void testHelloFacade() {
    doNothing().when(mock).printHello();
    obj.sayHello();
}

}

Rien, dans l'exemple précédent, nous devons instancier manuellement HelloFacadeImpl et l'assigner à HelloFacade, car HelloFacade est une interface. Dans le dernier exemple, nous pouvons simplement déclarer HelloFacadeImpl et Mokito l'instanciera pour nous. L'inconvénient de cette approche est que maintenant, l'unité sous test est impl-class et non interface.

1 votes

Est il y a un problème avec la solution ? L'article de blog dont vous faites le lien n'utilise pas @InjectMocks (relativement récent, bien qu'il soit antérieur à cet article de blog). Dans certaines circonstances, il peut être nécessaire de réorganiser les définitions des haricots. Je ne suis pas sûr de la question, en fin de compte.

0 votes

Spring n'a aucun problème avec Mockito. Ou vice versa.

0 votes

Je pense que dans la plupart des cas, vous devriez tester contre l'implémentation réelle plutôt que contre l'interface.

55voto

Adrian Shum Points 10784

Honnêtement, je ne suis pas sûr d'avoir bien compris votre question :P Je vais essayer de clarifier autant que possible ce que j'ai compris de votre question initiale :

Tout d'abord, dans la plupart des cas, vous ne devriez PAS avoir de souci avec le printemps. Vous avez rarement besoin d'impliquer Spring dans l'écriture de vos tests unitaires. Dans un cas normal, vous avez seulement besoin d'instancier le système sous test (SUT, la cible à tester) dans votre test unitaire, et d'injecter les dépendances du SUT dans le test aussi. Les dépendances sont généralement un mock/stub.

La méthode que vous avez suggérée à l'origine, et les exemples 2 et 3 font précisément ce que je décris ci-dessus.

Dans certains cas rares (comme les tests d'intégration, ou certains tests unitaires spéciaux), vous devez créer un contexte d'application Spring, et obtenir votre SUT à partir du contexte d'application. Dans ce cas, je pense que vous pouvez le faire :

1) Créez votre SUT dans le ctx de l'application Spring, obtenez une référence à celui-ci, et injectez-lui des mocks.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("test-app-ctx.xml")
public class FooTest {

    @Autowired
    @InjectMocks
    TestTarget sut;

    @Mock
    Foo mockFoo;

    @Before
    /* Initialized mocks */
    public void setup() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void someTest() {
         // ....
    }
}

ou

2) suivre la méthode décrite dans votre lien Tests d'intégration de Spring, création de Mock Objects . Cette approche consiste à créer des objets fantaisie dans le contexte de l'application de Spring, et vous pouvez obtenir l'objet fantaisie à partir du contexte de l'application pour effectuer votre stubbing/vérification :

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("test-app-ctx.xml")
public class FooTest {

    @Autowired
    TestTarget sut;

    @Autowired
    Foo mockFoo;

    @Test
    public void someTest() {
         // ....
    }
}

Les deux méthodes devraient fonctionner. La principale différence est que, dans le premier cas, les dépendances seront injectées après avoir traversé le cycle de vie de Spring, etc. (par exemple, l'initialisation du bean), tandis que dans le second cas, les dépendances sont injectées avant. Par exemple, si votre SUT implémente l'InitializingBean de Spring, et que la routine d'initialisation implique les dépendances, vous verrez la différence entre ces deux approches. Je crois qu'il n'y a pas de bon ou de mauvais pour ces 2 approches, tant que vous savez ce que vous faites.

Juste un supplément, @Mock, @Inject, MocktoJunitRunner etc. sont tous inutiles dans l'utilisation de Mockito. Ce sont juste des utilitaires pour vous éviter de taper Mockito.mock(Foo.class) et un tas d'invocations de setter.

0 votes

@Noam, jetez un coup d'oeil à cette réponse. Adrian, excellente réponse, merci :-)

2 votes

Je ne pense pas que 1) fonctionne. C'est-à-dire que lorsque vous utilisez à la fois @Autowired y @InjectMocks je vois les beans injectés par Spring, et non pas les mocks, dans le fichier TestTarget . (J'espérais que l'utilisation des deux annotations permettrait de seulement injecter un simulateur pour Foo mais utilise toujours les beans injectés par Spring par défaut pour toutes les autres dépendances qui sont câblées automatiquement dans l'application TestTarget mais ne sont pas simulés dans le test d'intégration. Pas de cigare, semble-t-il. Spring 3.1.2 ; Mockito 1.9.5)

0 votes

Bien que je n'aie pas essayé, je pense que cela devrait fonctionner. @Autowired est géré par le Spring JUnit Runner, qui est traité avant setup() . @InjectMocks est traité par MockitoAnnotaitons.initMocks(this) en setup() . Je ne vois aucune raison d'empêcher son fonctionnement. Je vais peut-être faire un essai pour confirmer que cela fonctionne :)

6voto

Brad Points 4963

Votre question semble porter sur le choix de l'approche à privilégier parmi les trois exemples que vous avez donnés.

Exemple 1 L'utilisation des TestUtils de Reflection n'est pas une bonne approche pour les applications suivantes Tests unitaires . Vous ne voulez vraiment pas charger le contexte de Spring du tout pour un test unitaire. Il suffit de simuler et d'injecter ce qui est nécessaire comme le montrent vos autres exemples.

Vous voulez charger le contexte de printemps si vous voulez faire quelque Tests d'intégration mais je préfère utiliser @RunWith(SpringJUnit4ClassRunner.class) pour effectuer le chargement du contexte avec @Autowired si vous avez besoin d'accéder à ses haricots de manière explicite.

Exemple 2 est une approche valable et l'utilisation de @RunWith(MockitoJUnitRunner.class) supprimera la nécessité de spécifier une méthode @Before et un appel explicite à la fonction MockitoAnnotations.initMocks(this);

Exemple 3 est une autre approche valide qui n'utilise pas @RunWith(...) . Vous n'avez pas instancié votre classe à tester. HelloFacadeImpl explicitement, mais vous auriez pu faire de même avec l'exemple 2.

Je vous suggère d'utiliser l'exemple 2 pour vos tests unitaires car il réduit l'encombrement du code. Vous pouvez revenir à la configuration la plus verbeuse si et quand vous êtes obligé de le faire.

4voto

geoand Points 10879

L'introduction de nouvelles fonctionnalités de test dans Spring 4.2.RC1 permet d'écrire des tests d'intégration Spring qui ne dépendent pas de la fonction SpringJUnit4ClassRunner . Vérifiez este une partie de la documentation.

Dans votre cas, vous pourriez écrire votre test d'intégration Spring et continuer à utiliser les mocks comme ceci :

@RunWith(MockitoJUnitRunner.class)
@ContextConfiguration("test-app-ctx.xml")
public class FooTest {

    @ClassRule
    public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();

    @Rule
    public final SpringMethodRule springMethodRule = new SpringMethodRule();

    @Autowired
    @InjectMocks
    TestTarget sut;

    @Mock
    Foo mockFoo;

    @Test
    public void someTest() {
         // ....
    }
}

2voto

Noam Points 642

Vous n'avez pas vraiment besoin de la MockitoAnnotations.initMocks(this); si vous utilisez mockito 1.9 ( ou plus récent ) - tout ce dont vous avez besoin est ceci :

@InjectMocks
private MyTestObject testObject;

@Mock
private MyDependentObject mockedObject;

Le site @InjectMocks injectera tous vos objets fantaisie dans l'objet MyTestObject objet.

10 votes

C'est l'utilisation de @RunWith(MockitoJUnitRunner.class) qui supprime la nécessité d'appeler MockitoAnnotations.initMocks(this) explicitement. Comme décrit ici l'annotation est disponible depuis la version 1.8.3

1 votes

@Brad Courir avec le coureur Mockito n'est pas toujours possible, cependant, c'est tout.

2voto

Adriaan Koster Points 6264

Voici mon bref résumé.

Si vous voulez écrire un test unitaire, n'utilisez pas un Spring applicationContext car vous ne voulez pas que des dépendances réelles soient injectées dans la classe que vous testez. Utilisez plutôt des mocks, soit avec la fonction @RunWith(MockitoJUnitRunner.class) au-dessus de la classe, ou avec l'annotation MockitoAnnotations.initMocks(this) dans la méthode @Before.

Si vous voulez écrire un test d'intégration, utilisez :

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("yourTestApplicationContext.xml")

Pour configurer votre contexte d'application avec une base de données en mémoire par exemple. Normalement, vous n'utilisez pas de mocks dans les tests d'intégration, mais vous pouvez le faire en utilisant la fonction MockitoAnnotations.initMocks(this) décrite ci-dessus.

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