Mockito, JUnit et Spring

J’ai commencé à apprendre sur Mockito seulement aujourd’hui. J’ai écrit quelques tests simples (avec JUnit, voir ci-dessous), mais je n’arrive pas à comprendre comment utiliser des objects fictifs dans les beans gérés par Spring. Quelle est la meilleure pratique pour travailler avec Spring. Comment devrais-je injecter une dépendance simulée à mon haricot?

Vous pouvez passer cette question à ma question .

Tout d’abord, ce que j’ai appris. C’est un très bon article Mocks Areless Stubs qui explique les bases (la vérification du comportement des contrôles de Mock et non la vérification d’état ). Ensuite, il y a un bon exemple ici

Ici Mockito et ici Matchers, vous pouvez trouver plus d’exemples.

Ce test

@Test public void testReal(){ List mockedList = mock(List.class); //stubbing //when(mockedList.get(0)).thenReturn("first"); mockedList.get(anyInt()); OngoingStubbing stub= when(null); stub.thenReturn("first"); //Ssortingng 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.

Retour à ma question Ici, Mockito Injecting mocks dans un haricot Spring quelqu’un essaie d’utiliser Springs ReflectionTestUtils.setField() , mais ici , les tests d’intégration Spring, Création d’objects Mock, nous avons la recommandation de changer le contexte de Spring.

Je n’ai pas vraiment compris les deux derniers liens … Est-ce que quelqu’un peut m’expliquer quel est le problème avec Spring avec Mockito? Quel est le problème avec 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’étais pas très clair. Je vais fournir 3 exemples de code pour clarifier mon auto: Supposons que nous ayons bean HelloWorld avec la méthode printHello() et bean HelloFacade avec la méthode sayHello qui sayHello les appels à la méthode HelloWorld printHello() .

Le premier exemple utilise le contexte de Spring et sans runner personnalisé, en utilisant ReflectionTestUtils pour l’dependency injections (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 appel explicite à MockitoAnnotations.initMocks(this); . Je vais également laisser tomber 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 le faire

 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(); } } 

Non, que dans un exemple précis, nous devons manuellement installer HelloFacadeImpl et l’atsortingbuer à HelloFacade, parce que HelloFacade est une interface. Dans le dernier exemple, nous pouvons simplement déclarer que HelloFacadeImpl et Mokito l’instancier pour nous. L’inconvénient de cette approche, à savoir l’unité-sous-test, est maintenant impl-class et non interface.

Honnêtement, je ne suis pas sûr de bien comprendre votre question: PI essaiera de clarifier autant que possible, d’après ce que je comprends de votre question initiale:

Tout d’abord, dans la plupart des cas, vous ne devriez PAS avoir de souci pour Spring. Il est rare que Spring doive participer à la rédaction de votre test unitaire. Dans des cas normaux, il vous suffit d’instancier le système sous test (SUT, la cible à tester) dans votre test unitaire et d’injecter également les dépendances du SUT dans le test. Les dépendances sont généralement un simulacre / stub.

Votre manière originale suggérée, et l’exemple 2, 3 fait 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 extraire votre SUT du contexte de l’application. Dans ce cas, je crois que vous pouvez:

1) Créez votre SUT dans l’application printanière ctx, faites-y référence et injectez-y des simulacres

 @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) Suivez la procédure décrite dans votre lien Tests d’intégration de spring, Création d’objects de simulation . Cette approche consiste à créer des simulacres dans le contexte d’application de Spring, et vous pouvez obtenir l’object simulé de l’application ctx pour effectuer votre stub / 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 manières devraient fonctionner. La principale différence est que le premier cas aura les dépendances injectées après avoir traversé le cycle de vie du spring, etc. Par exemple, si votre SUT implémente 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 deux approches, du moment que vous savez ce que vous faites.

Juste un supplément, @Mock, @Inject, MocktoJunitRunner, etc. sont tous inutiles dans l’utilisation de Mockito. Ce ne sont que des utilitaires pour vous sauver en tapant le Mockito.mock (Foo.class) et un tas d’invocations de setter.

Votre question semble être de savoir lequel des trois exemples que vous avez donnés est l’approche préférée.

L’exemple 1 utilisant le Test de reflection n’est pas une bonne approche pour les tests unitaires . Vous ne voulez vraiment pas charger le contexte Spring pour un test unitaire. Juste se moquer et injecter ce qui est requirejs comme indiqué par vos autres exemples.

Vous voulez charger le contexte Spring si vous voulez faire des tests d’intégration , mais je préférerais utiliser @RunWith(SpringJUnit4ClassRunner.class) pour effectuer le chargement du contexte avec @Autowired si vous avez besoin d’accéder explicitement à ses beans.

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

L’exemple 3 est une autre approche valide qui n’utilise pas @RunWith(...) . Vous n’avez pas instancié explicitement votre classe sous le test HelloFacadeImpl , mais vous auriez pu faire la même chose avec l’exemple 2.

Ma suggestion est d’utiliser l’exemple 2 pour vos tests unitaires car cela réduit l’encombrement du code. Vous pouvez revenir à la configuration plus détaillée si et quand vous êtes obligé de le faire.

L’introduction de nouvelles fonctionnalités de test dans Spring 4.2.RC1 permet d’écrire des tests d’intégration Spring qui ne reposent pas sur SpringJUnit4ClassRunner . Découvrez cette partie de la documentation.

Dans votre cas, vous pourriez écrire votre test d’intégration de spring et continuer à utiliser des simulacres comme celui-ci:

 @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() { // .... } } 

Vous n’avez pas vraiment besoin des MockitoAnnotations.initMocks(this); Si vous utilisez mockito 1.9 (ou plus récent), il vous suffit de ceci:

 @InjectMocks private MyTestObject testObject; @Mock private MyDependentObject mockedObject; 

L’annotation @InjectMocks injectera tous vos objects MyTestObject object MyTestObject .

Voici mon bref résumé.

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

Si vous souhaitez écrire un test d’intégration, utilisez:

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

Pour configurer votre contexte d’application avec une firebase database en mémoire par exemple. Normalement, vous n’utilisez pas de simulacre dans les tests d’intégration, mais vous pouvez le faire en utilisant l’ MockitoAnnotations.initMocks(this) décrite ci-dessus.

La différence entre si vous devez instancier votre @InjectMocks annoté @InjectMocks réside dans la version de Mockito, que vous utilisiez MockitoJunitRunner ou MockitoAnnotations.initMocks . Dans la version 1.9, qui gérera également l’injection de constructeur de vos champs @Mock , il fera l’instanciation pour vous. Dans les versions antérieures, vous devez l’instancier vous-même.

C’est comme ça que je fais des tests unitaires de mes haricots de spring. Il n’y a pas de problème. Les gens se heurtent à la confusion quand ils veulent utiliser les fichiers de configuration Spring pour faire l’injection des simulacres, ce qui croise le sharepoints tests unitaires et des tests d’intégration.

Et bien sûr, l’unité testée est un Impl. Vous devez tester une chose concrète, non? Même si vous le déclariez comme une interface, vous devriez instancier le vrai pour le tester. Maintenant, vous pourriez entrer dans des espions, qui sont des wraps stub / mock autour d’objects réels, mais cela devrait être pour les casiers d’angle.

Si vous migrez votre projet vers Spring Boot 1.4, vous pouvez utiliser une nouvelle annotation @MockBean pour simuler MyDependentObject . Avec cette fonctionnalité, vous pouvez supprimer les annotations @InjectMocks @Mock et @InjectMocks de votre test.