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.
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.
@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.
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;
}
}
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)
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() {
....
}
}
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.
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>
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>
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
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>
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.
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>
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;
}
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>
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.
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
_
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.
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 .
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é.
<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
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 :)
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.