Injection de Mockito Mock dans un haricot
j'aimerais injecter un objet Mockito mock dans un haricot à ressort (3+) pour les besoins de l'essai à l'unité avec JUnit. Mes dépendances de haricot sont actuellement injectées en utilisant l'annotation @Autowired
sur les champs membres privés.
j'ai envisagé d'utiliser ReflectionTestUtils.setField
mais l'instance fève que je souhaite injecter est en fait un proxy et ne déclare donc pas les champs membres privés de la classe cible. Je ne veux pas créer un setter public à la dépendance comme je vais alors modifier mon interface purement pour les fins de test.
j'ai suivi quelques conseil donné par la communauté de Spring mais la simulation ne se crée pas et l'auto-câblage é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 mets la valeur constructor-arg
à quelque chose d'invalide, aucune erreur ne se produit lors du démarrage du contexte de l'application.
23 réponses
La meilleure façon est:
<bean id="dao" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="com.package.Dao" />
</bean>
mise à Jour
Dans le contexte d'un fichier cette maquette doit figurer avant toute autocâblés champ selon elle est déclarée.
@InjectMocks
private MyTestObject testObject;
@Mock
private MyDependentObject mockedObject;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
ceci injectera tout objet moqué dans la classe 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 en utilisant la config Java de printemps 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;
}
}
:
@Service
public class MyService {
@Autowired
private MyDAO myDAO;
// etc
}
vous pouvez faire charger la classe testée par autowiring, simuler la dépendance avec Mockito, puis utiliser les Réflectiontestutils du ressort pour injecter la simulation dans la classe testée.
@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 fonctionnera pas avec les services derrière un proxy (annoté avec @Transactional
, ou Cacheable
, par exemple). Ceci a été fixé par SPR-14050 .
pour les versions précédentes, une solution consiste à déballer le proxy, comme décrit ici: l'annotation transactionnelle évite de se moquer des services (ce que ReflectionTestUtils.setField
fait par défaut maintenant)
si vous utilisez la botte de printemps 1.4, il a une façon impressionnante de le faire. Il suffit d'utiliser la nouvelle marque @SpringBootTest
sur votre classe et @MockBean
sur le terrain et la botte à ressort créera un mock de ce type et il 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");
String reverse = reverser.reverseSomeCall();
assertThat(reverse).isEqualTo("kcom");
}
}
par contre, 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éer un haricot @Configuration
qui injecte vos moqueries dans le contexte du printemps:
@Configuration
@Profile("useMocks")
public class MockConfigurer {
@Bean
@Primary
public MyBean myBeanSpy() {
return mock(MyBean.class);
}
}
en utilisant @Primary
annotation vous dites au printemps que ce haricot 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 la simulation et celles qui utiliseront la vraie fève.
enfin, dans votre test, activez userMocks
profil:
@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 mock mais le vrai bean, il suffit de ne pas activer useMocks
profil:
@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 the MockitoJUnitRunner et je construis des objets @ 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.
je annotation @RunWith la SpringJUnit4Runner pour les tests d'intégration seulement maintenant.
je note qu'il ne semble pas être en mesure d'injecter Liste de la même manière que le Printemps. Il recherche seulement un objet Mock qui satisfait à la liste, et n'injectera pas une liste d'objets Mock. La solution pour moi était d'utiliser un @Spy contre une liste instanciée manuellement, et manuellement .ajouter le simulacre de l'objet(s) à cette liste pour les tests unitaires. Peut-être que c'était intentionnel, parce que ça m'a forcé à faire très attention à ce qu'on se moquait ensemble.
mise à Jour: Il y a mieux maintenant, plus propres solutions à 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 j'avais était dû à la méthode Mockito.mock(Class c)
déclarant un type de retour de Object
. Par conséquent le ressort ne peut pas inférer le type de haricot du type de retour de la méthode d'usine.
Ronen la solution est pour créer une implémentation FactoryBean
qui renvoie les moqueries. L'interface FactoryBean
permet à Spring d'interroger le type d'objets créés par la fève d'usine.
Mon moqué de haricot définition ressemble maintenant à:
<bean id="mockDaoFactory" name="dao" class="com.package.test.MocksFactory">
<property name="type" value="com.package.Dao" />
</bean>
à compter du printemps 3.2, cette question ne se pose plus. Spring prend maintenant en charge L'Autowiring des résultats des méthodes génériques d'usine. Voir la section intitulée "méthodes génériques D'usine" dans ce billet de blog: http://spring.io/blog/2012/11/07/spring-framework-3-2-rc1-new-testing-features / .
le point clé est:
au printemps 3.2, les types génériques de retour pour les méthodes d'usine sont maintenant correctement déduit, et l'autodiagnostic par type pour les moqueurs devrait fonctionner comme suit: devrait. En conséquence, des solutions de rechange personnalisées telles que MockitoFactoryBean, EasyMockFactoryBean, ou Springockito sont probablement pas plus nécessaire.
ce qui veut dire que cela devrait être dans la boîte:
<bean id="dao" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="com.package.Dao" />
</bean>
ci - dessous code fonctionne avec autowiring-il n'est pas la version la plus courte mais utile quand il devrait fonctionner seulement avec ressort standard/jars 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>
si vous utilisez spring > = 3.0 , essayez D'utiliser @Configuration
annotation 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 voulez pas utiliser la source @ImportResource, il peut être fait dans l'autre sens aussi:
<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-référence: Java-based container configuration
je peux faire ce qui suit en utilisant Mockito:
<bean id="stateMachine" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="com.abcd.StateMachine"/>
</bean>
peut-être pas la solution parfaite, mais j'ai tendance à ne pas utiliser le ressort pour faire DI pour les tests unitaires. les dépendances pour un seul haricot (la classe à l'essai) ne sont généralement pas trop complexes donc je fais juste l'injection directement dans le code de test.
affichage de quelques exemples basés sur les approches ci-dessus
Avec Ressort:
@ContextConfiguration(locations = { "classpath:context.xml" })
@RunWith(SpringJUnit4ClassRunner.class)
public class TestServiceTest {
@InjectMocks
private TestService testService;
@Mock
private TestService2 testService2;
}
Sans Ressort:
@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 s'applique qu'aux versions à ressort antérieures à 3.2.
j'ai cherché pendant un certain temps une solution plus définitive à cela. Ce blog semble couvrir tous mes besoins, et ne pas compter sur la commande de haricot déclarations. Tout le mérite revient à Mattias Severson. http://www.jayway.com/2011/11/30/spring-integration-tests-part-i-creating-mock-objects /
essentiellement, mettre en œuvre 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;
}
}
mettre à jour votre configuration de printemps avec ce qui suit:
<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 Springockito rythme de développement et nombre de questions ouvertes , je serais peu inquiet de l'introduire dans ma pile de suite de nos jours. Le fait que la dernière version ait été faite avant la version du printemps 4 soulève des questions comme "est-il possible de l'intégrer facilement avec le printemps 4?". Je ne sais pas, parce que je ne l'ai pas essayé. Je préfère une approche purement printanière si j'ai besoin de simuler un test d'intégration.
Il ya une option de faux haricot à ressort avec des caractéristiques de ressort simple. Vous devez utiliser @Primary
, @Profile
et @ActiveProfiles
annotations. j'ai écrit un billet de blog sur le sujet.
j'ai trouvé une réponse similaire à teabot pour créer un MockFactory qui fournit les moqueries. J'ai utilisé l'exemple suivant pour créer la fausse usine (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
<bean id="someFacade" class="nl.package.test.MockFactory">
<property name="type" value="nl.package.someFacade"/>
</bean>
cela aide aussi à prévenir que le ressort veut résoudre les injections de la moqué de haricot.
<bean id="mockDaoFactory" name="dao" class="com.package.test.MocksFactory">
<property name="type" value="com.package.Dao" />
</bean>
ce ^ fonctionne parfaitement bien si déclarée d'abord/au début du fichier XML. Mockito 1.9.0 / Printemps 3.0.5
j'utilise une combinaison de l'approche utilisée dans answer by Markust et un simple helper implémentation de ImportBeanDefinitionRegistrar
qui cherche une annotation personnalisée ( @MockedBeans
) dans laquelle on peut spécifier quelles classes doivent être moquées. Je crois que cette approche résulte en un test unitaire concis avec une partie du code boilerplate lié à la moquerie enlevé.
voici à quoi ressemble un test d'unité d'échantillonnage 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 faire ce vous devez définir deux classes d'aide simples-l'annotation personnalisée ( @MockedBeans
) et un
ImportBeanDefinitionRegistrar
la mise en œuvre. La définition de l'annotation @MockedBeans
doit être annotée par @Import(CustomImportBeanDefinitionRegistrar.class)
et la définition de ImportBeanDefinitionRgistrar
doit être ajoutée à la configuration de la méthode registerBeanDefinitions
.
si vous aimez l'approche, vous pouvez trouver l'échantillon implémentations sur mon blogpost .
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 en l'expliquant.
je suggère de migrer votre projet vers la botte de printemps 1.4. Après cela, vous pouvez utiliser la nouvelle annotation @MockBean
pour simuler votre com.package.Dao
Aujourd'hui j'ai découvert qu'un contexte de printemps où j'ai déclaré un avant les haricots Mockito, ne se chargeait pas. Après avoir déplacé L'après-Mock, le contexte app a été chargé avec succès. Prendre soin :)
pour mémoire, tous mes tests fonctionnent correctement en rendant le montage paresseux-initialisé, 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 le raisonnement est celui Mattias explique ici (au bas du poteau), qu'une solution de contournement est en train de changer l'ordre dans lequel les haricots sont déclarés - l'initialisation paresseuse est" en quelque sorte " avoir le montage déclaré à la fin.
si vous utilisez L'Injection du contrôleur, assurez-vous que vos variables locales ne sont pas" finales "