Injection de mockito dans une fève de spring

Je voudrais injecter un object Mockito dans un bean Spring (3+) à des fins de test unitaire avec JUnit. Les dépendances de mon bean sont actuellement injectées à l’aide de l’annotation @Autowired sur les champs de membre privé.

J’ai envisagé d’utiliser ReflectionTestUtils.setField mais l’instance du bean que je souhaite injecter est en fait un proxy et ne déclare donc pas les champs de membre privé de la classe cible. Je ne souhaite pas créer un setter public pour la dépendance car je modifierai alors mon interface uniquement à des fins de test.

J’ai suivi certains conseils donnés par la communauté Spring mais le simulacre ne se crée pas et le câblage automatique échoue:

    

L’erreur que je rencontre actuellement est la suivante:

 ... Caused by: org...NoSuchBeanDefinitionException: No matching bean of type [com.package.Dao] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: { @org...Autowired(required=true), @org...Qualifier(value=dao) } at org...DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(D...y.java:901) at org...DefaultListableBeanFactory.doResolveDependency(D...y.java:770) 

Si je mets la valeur constructor-arg à quelque chose d’invalide, aucune erreur ne se produit lors du démarrage du contexte d’application.

Le meilleur moyen est de:

    

Mettre à jour
Dans le fichier de contexte, ce simulacre doit être répertorié avant tout champ automatiquement sélectionné en fonction de sa déclaration.

 @InjectMocks private MyTestObject testObject; @Mock private MyDependentObject mockedObject; @Before public void setup() { MockitoAnnotations.initMocks(this); } 

Cela injectera tous les objects simulés dans la classe de test. Dans ce cas, il injectera mockedObject dans le testObject. Cela a été mentionné ci-dessus mais voici le code.

J’ai une solution très simple avec Spring Java Config et Mockito:

 @Configuration public class TestConfig { @Mock BeanA beanA; @Mock BeanB beanB; public TestConfig() { MockitoAnnotations.initMocks(this); //This is a key } //You basically generate getters and add @Bean annotation everywhere @Bean public BeanA getBeanA() { return beanA; } @Bean public BeanB getBeanB() { return beanB; } } 

Donné:

 @Service public class MyService { @Autowired private MyDAO myDAO; // etc } 

Vous pouvez faire charger la classe en cours de test via la fonction de sélection automatique, simuler la dépendance avec Mockito, puis utiliser ReflectionTestUtils de Spring pour injecter la maquette dans la classe en cours de test.

 @ContextConfiguration(classes = { MvcConfiguration.class }) @RunWith(SpringJUnit4ClassRunner.class) public class MyServiceTest { @Autowired private MyService myService; private MyDAO myDAOMock; @Before public void before() { myDAOMock = Mockito.mock(MyDAO.class); ReflectionTestUtils.setField(myService, "myDAO", myDAOMock); } // etc } 

Veuillez noter qu’avant Spring 4.3.1, cette méthode ne fonctionnera pas avec les services derrière un proxy (annotés avec @Transactional ou Cacheable , par exemple). Cela a été corrigé par SPR-14050 .

Pour les versions antérieures, une solution consiste à déballer le proxy, comme décrit ici: L’annotation transactionnelle évite que les services ne soient simulés (ce que fait ReflectionTestUtils.setField par défaut maintenant)

Si vous utilisez Spring Boot 1.4, il existe un moyen formidable de le faire. Utilisez simplement la nouvelle marque @SpringBootTest sur votre classe et @MockBean sur le terrain et Spring Boot créera un simulacre de ce type et l’injectera dans le contexte (au lieu d’injecter l’original):

 @RunWith(SpringRunner.class) @SpringBootTest public class MyTests { @MockBean private RemoteService remoteService; @Autowired private Reverser reverser; @Test public void exampleTest() { // RemoteService has been injected into the reverser bean given(this.remoteService.someCall()).willReturn("mock"); Ssortingng reverse = reverser.reverseSomeCall(); assertThat(reverse).isEqualTo("kcom"); } } 

D’un autre côté, si vous n’utilisez pas Spring Boot ou si vous utilisez une version précédente, vous devrez faire un peu plus de travail:

Créez un bean @Configuration qui injecte vos @Configuration dans le contexte Spring:

 @Configuration @Profile("useMocks") public class MockConfigurer { @Bean @Primary public MyBean myBeanSpy() { return mock(MyBean.class); } } 

En @Primary annotation @Primary vous @Primary à Spring que ce bean a la priorité si aucun qualificatif n’est spécifié.

Assurez-vous d’annoter la classe avec @Profile("useMocks") afin de contrôler les classes qui utiliseront le simulacre et celles qui utiliseront le bean réel.

Enfin, dans votre test, activez le profil userMocks :

 @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = {Application.class}) @WebIntegrationTest @ActiveProfiles(profiles={"useMocks"}) public class YourIntegrationTestIT { @Inject private MyBean myBean; //It will be the mock! @Test public void test() { .... } } 

Si vous ne voulez pas utiliser le simulacre mais le vrai bean, n’activez simplement pas le profil useMocks :

 @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = {Application.class}) @WebIntegrationTest public class AnotherIntegrationTestIT { @Inject private MyBean myBean; //It will be the real implementation! @Test public void test() { .... } } 

Depuis 1.8.3 Mockito a @InjectMocks – c’est incroyablement utile. Mes tests JUnit sont @RunWith les objects MockitoJUnitRunner et I build @Mock qui satisfont toutes les dépendances de la classe testée, qui sont tous injectés lorsque le membre privé est annoté avec @InjectMocks.

J’ai @RunWith le SpringJUnit4Runner pour les tests d’intégration seulement maintenant.

Je noterai qu’il ne semble pas pouvoir injecter List de la même manière que Spring. Il recherche uniquement un object Mock qui satisfait à la liste et n’injecte pas de liste d’objects Mock. La solution de contournement pour moi consistait à utiliser un @Spy avec une liste instanciée manuellement et à append manuellement les objects simulés à cette liste pour les tests unitaires. Peut-être que c’était intentionnel, parce que cela m’a forcé à porter une attention particulière à ce qui était moqué.

Mise à jour: Il existe maintenant des solutions plus efficaces pour résoudre ce problème. Veuillez d’abord considérer les autres réponses.

J’ai finalement trouvé une réponse à cela par Ronen sur son blog. Le problème que je rencontrais est dû à la méthode Mockito.mock(Class c) déclarant un type d’ Object de retour. Par conséquent, Spring ne peut pas déduire le type de bean du type de retour de la méthode d’usine.

La solution de Ronen est de créer une implémentation FactoryBean qui retourne des simulacres. L’interface FactoryBean permet à Spring d’interroger le type d’objects créés par le bean factory.

Ma définition de haricot simulé ressemble maintenant à:

    

À partir du spring 3.2, ce n’est plus un problème. Spring prend désormais en charge Autowiring des résultats des méthodes d’usine génériques. Voir la section intitulée “Méthodes d’usine génériques” dans cet article de blog: http://spring.io/blog/2012/11/07/spring-framework-3-2-rc1-new-testing-features/ .

Le point clé est:

Dans Spring 3.2, les types de retour génériques pour les méthodes d’usine sont désormais correctement déduits, et la génération automatique par type pour les simulations devrait fonctionner comme prévu. Par conséquent, les solutions de contournement personnalisées telles que MockitoFactoryBean, EasyMockFactoryBean ou Springockito ne sont probablement plus nécessaires.

Ce qui signifie que cela devrait fonctionner hors de la boîte:

    

Le code ci-dessous fonctionne avec le démarrage automatique – il ne s’agit pas de la version la plus courte, mais utile si elle ne doit fonctionner qu’avec des bocaux Spring / Mockito standard.

        com.package.Dao   

Si vous utilisez spring> = 3.0 , essayez d’utiliser l’annotation Springs @Configuration pour définir une partie du contexte d’application

 @Configuration @ImportResource("com/blah/blurk/rest-of-config.xml") public class DaoTestConfiguration { @Bean public ApplicationService applicationService() { return mock(ApplicationService.class); } } 

Si vous ne voulez pas utiliser @ImportResource, cela peut être fait dans l’autre sens:

      

Pour plus d’informations, jetez un coup d’œil à spring-framework-reference: Configuration de conteneur Java

Je peux faire ce qui suit en utilisant Mockito:

    

Peut-être pas la solution parfaite, mais j’ai tendance à ne pas utiliser le spring pour faire des DI pour les tests unitaires. les dépendances pour un seul bean (la classe testée) ne sont généralement pas trop complexes, alors je fais l’injection directement dans le code de test.

Affichage de quelques exemples basés sur les approches ci-dessus

Avec le spring:

 @ContextConfiguration(locations = { "classpath:context.xml" }) @RunWith(SpringJUnit4ClassRunner.class) public class TestServiceTest { @InjectMocks private TestService testService; @Mock private TestService2 testService2; } 

Sans spring:

 @RunWith(MockitoJUnitRunner.class) public class TestServiceTest { @InjectMocks private TestService testService = new TestServiceImpl(); @Mock private TestService2 testService2; } 

Mise à jour – nouvelle réponse ici: https://stackoverflow.com/a/19454282/411229 . Cette réponse s’applique uniquement aux versions antérieures à la version 3.2.

J’ai cherché pendant un moment une solution plus définitive à ce problème. Ce billet semble couvrir tous mes besoins et ne repose pas sur la commande de déclarations de haricots. Tout le crédit à Mattias Severson. http://www.jayway.com/2011/11/30/spring-integration-tests-part-i-creating-mock-objects/

Fondamentalement, implémentez un FactoryBean

 package com.jayway.springmock; import org.mockito.Mockito; import org.springframework.beans.factory.FactoryBean; /** * A {@link FactoryBean} for creating mocked beans based on Mockito so that they * can be {@link @Autowired} into Spring test configurations. * * @author Mattias Severson, Jayway * * @see FactoryBean * @see org.mockito.Mockito */ public class MockitoFactoryBean implements FactoryBean { private Class classToBeMocked; /** * Creates a Mockito mock instance of the provided class. * @param classToBeMocked The class to be mocked. */ public MockitoFactoryBean(Class classToBeMocked) { this.classToBeMocked = classToBeMocked; } @Override public T getObject() throws Exception { return Mockito.mock(classToBeMocked); } @Override public Class getObjectType() { return classToBeMocked; } @Override public boolean isSingleton() { return true; } } 

Ensuite, mettez à jour votre configuration de spring avec les éléments suivants:

       

En regardant le rythme de développement de Springockito et le nombre de problèmes en suspens , je serais un peu inquiet de l’introduire dans ma suite de tests. Le fait que la dernière version ait été réalisée avant la sortie de Spring 4 soulève des questions telles que “Est-il possible de l’intégrer facilement avec Spring 4?”. Je ne sais pas, parce que je ne l’ai pas essayé. Je préfère l’approche Spring pure si j’ai besoin de simuler le haricot de spring dans le test d’intégration.

Il y a une option pour faire semblant de haricot de spring avec des caractéristiques simples de spring. Vous devez utiliser les @Primary , @Profile et @ActiveProfiles pour cela. J’ai écrit un blog sur le sujet.

J’ai trouvé une réponse similaire à celle de teabot pour créer une MockFactory qui fournit les simulacres. J’ai utilisé l’exemple suivant pour créer la fabrique de simulateurs (puisque le lien vers narkisr est mort): http://hg.randompage.org/java/src/407e78aa08a0/projects/bookmarking/backend/spring/src/test/java/ org / randompage / bookmarking / backend / testUtils / MocksFactory.java

    

Cela aide également à empêcher que le spring ne veuille résoudre les injections du haricot simulé.

    

ceci fonctionne parfaitement si déclaré / tôt dans le fichier XML. Mockito 1.9.0 / Printemps 3.0.5

J’utilise une combinaison de l’approche utilisée dans la réponse de Markus T et une simple implémentation d’assistance de ImportBeanDefinitionRegistrar qui recherche une annotation personnalisée ( @MockedBeans ) dans laquelle on peut spécifier les classes à simuler. Je crois que cette approche se traduit par un test unitaire concis avec une partie du code passe-partout associé à la moquerie supprimée.

Voici comment un test d’unité d’échantillonnage ressemble à cette approche:

 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(loader=AnnotationConfigContextLoader.class) public class ExampleServiceIntegrationTest { //our service under test, with mocked dependencies injected @Autowired ExampleService exampleService; //we can autowire mocked beans if we need to used them in tests @Autowired DependencyBeanA dependencyBeanA; @Test public void testSomeMethod() { ... exampleService.someMethod(); ... verify(dependencyBeanA, times(1)).someDependencyMethod(); } /** * Inner class configuration object for this test. Spring will read it thanks to * @ContextConfiguration(loader=AnnotationConfigContextLoader.class) annotation on the test class. */ @Configuration @Import(TestAppConfig.class) //TestAppConfig may contain some common integration testing configuration @MockedBeans({DependencyBeanA.class, DependencyBeanB.class, AnotherDependency.class}) //Beans to be mocked static class ContextConfiguration { @Bean public ExampleService exampleService() { return new ExampleService(); //our service under test } } } 

Pour ce faire, vous devez définir deux classes d’assistance simples: une annotation personnalisée ( @MockedBeans ) et une implémentation personnalisée ImportBeanDefinitionRegistrar . @MockedBeans définition d’annotation @MockedBeans doit être annotée avec @Import(CustomImportBeanDefinitionRegistrar.class) et ImportBeanDefinitionRgistrar doit append des définitions de beans ImportBeanDefinitionRgistrar à la configuration de sa méthode registerBeanDefinitions .

Si vous aimez l’approche, vous pouvez trouver des exemples d’ implémentations sur mon blog .

J’ai développé une solution basée sur la proposition de Kresimir Nesek. J’ai ajouté une nouvelle annotation @EnableMockedBean afin de rendre le code un peu plus propre et modulaire.

 @EnableMockedBean @SpringBootApplication @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes=MockedBeanTest.class) public class MockedBeanTest { @MockedBean private HelloWorldService helloWorldService; @Autowired private MiddleComponent middleComponent; @Test public void helloWorldIsCalledOnlyOnce() { middleComponent.getHelloMessage(); // THEN HelloWorldService is called only once verify(helloWorldService, times(1)).getHelloMessage(); } } 

J’ai écrit un article l’ expliquant.

Je suggère de migrer votre projet vers Spring Boot 1.4. Après cela, vous pouvez utiliser une nouvelle annotation @MockBean pour com.package.Dao votre com.package.Dao

Aujourd’hui, j’ai découvert qu’un contexte printanier où je déclarais un avant les haricots Mockito ne parvenait pas à charger. Après avoir déplacé le APRÈS les simulations, le contexte de l’application a été chargé avec succès. Prends soin 🙂

Pour la petite histoire, tous mes tests fonctionnent correctement en faisant simplement initialiser le projecteur, par exemple:

          

Je suppose que la justification est celle que Mattias explique ici (en bas de l’article), qu’une solution consiste à changer l’ordre dans lequel les beans sont déclarés – l’initialisation paresseuse est “en quelque sorte” que le fixture soit déclaré à la fin.

Si vous utilisez l’injection de contrôleur, assurez-vous que vos variables locales ne sont PAS “définitives”