web-dev-qa-db-fra.com

Injecter Mockito se moque d'un haricot printanier

Je souhaite injecter un objet fictif Mockito dans un haricot Spring (3+) aux fins des tests unitaires avec JUnit. Les dépendances de mes beans sont actuellement injectées à l'aide de l'annotation @Autowired sur les champs de membres privés.

J'ai envisagé d'utiliser ReflectionTestUtils.setField, mais l'instance de bean que je souhaite injecter est en réalité 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 à 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 la maquette n'est pas créée et le câblage automatique échoue:

<bean id="dao" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="com.package.Dao" />
</bean>

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 règle la valeur constructor-arg sur une valeur non valide, aucune erreur ne se produit lors du démarrage du contexte de l'application.

268
teabot

Le meilleur moyen est:

<bean id="dao" class="org.mockito.Mockito" factory-method="mock"> 
    <constructor-arg value="com.package.Dao" /> 
</bean> 

Mise à jour
Dans le fichier de contexte, ce modèle doit être répertorié avant tout champ auto-câblé, en fonction de sa déclaration.

125
amra
@InjectMocks
private MyTestObject testObject;

@Mock
private MyDependentObject mockedObject;

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

Cela injectera tous les objets 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.

107
Greg Beauchamp

J'ai une solution très simple utilisant 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;
    }
}
62
Piotr Gwiazda

Donné:

@Service
public class MyService {
    @Autowired
    private MyDAO myDAO;

    // etc
}

Vous pouvez charger la classe en cours de test via le câblage automatique, simuler la dépendance avec Mockito, puis utiliser ReflectionTestUtils de Spring pour injecter le modèle 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 le printemps 4.3.1, cette méthode ne fonctionnerait pas avec les services derrière un proxy (annoté avec @Transactional, ou Cacheable, par exemple). Ce problème a été résolu par SPR-14050 .

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

41
Paul Croarkin

Si vous utilisez Spring Boot 1.4, c'est un moyen formidable de le faire. Utilisez simplement la nouvelle marque @SpringBootTest sur votre classe et @MockBean sur le terrain. Spring Boot créera une simulation de ce type et l'injectera dans le contexte (au lieu d'injecter celle d'origine):

@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");
        String reverse = reverser.reverseSomeCall();
        assertThat(reverse).isEqualTo("kcom");
    }

}

D'autre part, si vous n'utilisez pas Spring Boot ou utilisez une version précédente, vous devrez effectuer un peu plus de travail:

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

@Configuration
@Profile("useMocks")
public class MockConfigurer {

    @Bean
    @Primary
    public MyBean myBeanSpy() {
        return mock(MyBean.class);
    }
}

En utilisant l'annotation @Primary, vous indiquez à Spring que ce bean est prioritaire si aucun qualificateur n'est spécifié.

Assurez-vous d’annoter la classe avec @Profile("useMocks") afin de contrôler les classes qui utiliseront la maquette et celles qui utiliseront le véritable haricot.

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 la maquette mais le vrai haricot, n'activez pas simplement 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() {
        ....
    }
}
34
jfcorugedo

Depuis 1.8. Mockito a @InjectMocks - c'est incroyablement utile. Mes tests JUnit sont @RunWith la MockitoJUnitRunner et j’ai construit @Mock objets qui satisfont à toutes les dépendances de la classe en cours de test, lesquelles sont toutes injectées lorsque le membre privé est annoté avec @InjectMocks.

Je @RunWith le SpringJUnit4Runner pour les tests d'intégration seulement maintenant.

Je noterai qu'il ne semble pas pouvoir injecter List<T> de la même manière que Spring. Il recherche uniquement un objet Mock qui satisfait la List, et n'injectera pas de liste d'objets Mock. La solution pour moi consistait à utiliser un @Spy contre une liste instanciée manuellement et à ajouter manuellement le ou les objets fictifs à cette liste à des fins de test unitaire. C'était peut-être intentionnel, parce que cela m'obligeait certainement à faire très attention à ce que l'on se moquait ensemble.

19
Doug Moscrop

Mise à jour: Il existe maintenant de meilleures solutions plus propres à ce problème. S'il vous plaît examiner les autres réponses en premier.

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 de retour de Object. Par conséquent, Spring ne peut pas déduire le type de bean à partir du type de retour de la méthode usine.

La solution de Ronen consiste à créer une implémentation FactoryBean qui retourne des mocks. L'interface FactoryBean permet à Spring d'interroger le type d'objets créés par le bean factory.

Ma définition de bean mocked ressemble maintenant à:

<bean id="mockDaoFactory" name="dao" class="com.package.test.MocksFactory">
    <property name="type" value="com.package.Dao" />
</bean>
13
teabot

Depuis le printemps 3.2, ce n'est plus un problème. Spring prend désormais en charge le câblage automatique des résultats des méthodes d'usine génériques. Voir la section intitulée "Méthodes génériques d'usine" de 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 maintenant correctement déduits et le câblage automatique par type pour les simulacres doit 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:

<bean id="dao" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="com.package.Dao" />
</bean>
11
Ryan Walls

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

@Configuration
@ImportResource("com/blah/blurk/rest-of-config.xml")
public class DaoTestConfiguration {

    @Bean
    public ApplicationService applicationService() {
        return mock(ApplicationService.class);
    }

}

Si vous ne souhaitez pas utiliser @ImportResource, vous pouvez également procéder de manière inverse:

<beans>
    <!-- rest of your config -->

    <!-- the container recognize this as a Configuration and adds it's beans 
         to the container -->
    <bean class="com.package.DaoTestConfiguration"/>
</beans>

Pour plus d'informations, consultez spring-framework-reference: configuration de conteneur basée sur Java

9
Markus T

Le code ci-dessous fonctionne avec l'auto-câblage - ce n'est pas la version la plus courte, mais il est utile lorsqu'il doit fonctionner uniquement avec des bocaux standard Spring/Mockito.

<bean id="dao" class="org.springframework.aop.framework.ProxyFactoryBean">
   <property name="target"> <bean class="org.mockito.Mockito" factory-method="mock"> <constructor-arg value="com.package.Dao" /> </bean> </property>
   <property name="proxyInterfaces"> <value>com.package.Dao</value> </property>
</bean> 
9
Kamil

Ce n’est peut-être pas la solution idéale, mais j’ai tendance à ne pas utiliser le ressort pour faire de la DI pour les tests unitaires. les dépendances pour un seul haricot (la classe à tester) ne sont généralement pas trop complexes, je fais donc l'injection directement dans le code de test.

8
Angelo Genovese

Je peux faire ce qui suit avec Mockito:

<bean id="stateMachine" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="com.abcd.StateMachine"/>
</bean>
7
Alexander

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

Avec le printemps:

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

Sans printemps:

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

Update - nouvelle réponse ici: https://stackoverflow.com/a/19454282/411229 . Cette réponse ne concerne que ceux des versions Spring antérieures à 3.2.

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

Fondamentalement, implémenter 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<T> implements FactoryBean<T> {

    private Class<T> classToBeMocked;

    /**
     * Creates a Mockito mock instance of the provided class.
     * @param classToBeMocked The class to be mocked.
     */
    public MockitoFactoryBean(Class<T> 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;
    }
}

Prochaine mise à jour de votre config de printemps avec les éléments suivants:

<beans...>
    <context:component-scan base-package="com.jayway.example"/>

    <bean id="someDependencyMock" class="com.jayway.springmock.MockitoFactoryBean">
        <constructor-arg name="classToBeMocked" value="com.jayway.example.SomeDependency" />
    </bean>
</beans>
2
Ryan Walls

En regardant rythme de développement Springockito et nombre de questions en suspens , je serais un peu inquiet de l'introduire dans ma pile de suites de tests de nos jours. Le fait que la dernière version ait été réalisée avant la publication de Spring 4 soulève des questions telles que "Est-il possible de l'intégrer facilement à Spring 4?". Je ne sais pas, parce que je ne l'ai pas essayé. Je préfère l'approche purement printanière si je dois me moquer de Spring bean lors d'un test d'intégration.

Il existe une option pour simuler le haricot Spring avec de simples caractéristiques Spring. Vous devez utiliser les annotations @Primary, @Profile et @ActiveProfiles. J'ai écrit un article de blog sur le sujet.

2
luboskrnac

Je suggérerais de migrer votre projet vers Spring Boot 1.4. Après cela, vous pouvez utiliser la nouvelle annotation @MockBean pour simuler votre _com.package.Dao_

1
luboskrnac

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 post l'expliquant.

1
Alfredo Diaz

J'utilise une combinaison de l'approche utilisée dans la réponse de Markus T et d'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 aboutit à un test unitaire concis avec une partie du code standard associée au moquage supprimée.

Voici à quoi ressemble un exemple de test unitaire avec 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. La définition d'annotation @MockedBeans doit être annotée avec @Import(CustomImportBeanDefinitionRegistrar.class) et la ImportBeanDefinitionRgistrar doit ajouter des définitions de beans simulées à la configuration dans sa méthode registerBeanDefinitions.

Si vous aimez l'approche, vous pouvez trouver un exemple implémentations sur mon blogpost .

1
Kresimir Nesek

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 factice (car les liens vers narkisr sont morts): http://hg.randompage.org/Java/src/407e78aa08a0/projects/bookmarking/backend/spring/src/ test/Java/org/randompage/bookmarking/backend/testUtils/MocksFactory.Java

<bean id="someFacade" class="nl.package.test.MockFactory">
    <property name="type" value="nl.package.someFacade"/>
</bean>

Cela permet également d'éviter que Spring souhaite résoudre les injections à partir du haricot simulé.

1
Renso Lohuis
<bean id="mockDaoFactory" name="dao" class="com.package.test.MocksFactory">
    <property name="type" value="com.package.Dao" />
</bean>

cela fonctionne parfaitement si est déclaré premier/tôt dans le fichier XML. Mockito 1.9.0/Spring 3.0.5

1
almondandapricot

Aujourd'hui, j'ai découvert qu'un contexte printanier dans lequel je déclarais ne pas être chargé avant les haricots Mockito échouait. Après avoir déplacé APRES les simulacres, le contexte de l'application a été chargé avec succès. Prends soin de toi :)

0
Daniele Dellafiore

Pour mémoire, tous mes tests fonctionnent correctement en effectuant simplement une initialisation paresseuse, par exemple:

<bean id="fixture"
      class="it.tidalwave.northernwind.rca.embeddedserver.impl.DefaultEmbeddedServer"
      lazy-init="true" /> <!-- To solve Mockito + Spring problems -->

<bean class="it.tidalwave.messagebus.aspect.spring.MessageBusAdapterFactory" />

<bean id="applicationMessageBus"
      class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="it.tidalwave.messagebus.MessageBus" />
</bean>

<bean class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="javax.servlet.ServletContext" />
</bean>

Je suppose que la raison est celle expliquée par Mattias ici (au bas de l'article), qu'une solution de contournement modifie l'ordre dans lequel les beans sont déclarés - l'initialisation paresseuse est "en quelque sorte" avec le montage déclaré à la fin.

0
Fabrizio Giudici