Incapable de se moquer de la classe du Service de Spring MVC Contrôleur de tests
J'ai un Printemps 3.2 application MVC et je suis en utilisant le Spring MVC framework de test pour tester GET et POST demandes sur les actions de mes contrôleurs. Je suis à l'aide de Mockito, à se moquer de Services, mais je suis au motif que l'on se moque de sont ignorés et que ma réelle de la couche de Service est utilisé (et, en conséquence, la base de données est d'être touché).
Le code dans mon test du Contrôleur:
package name.hines.steven.medical_claims_tracker.controllers;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
import name.hines.steven.medical_claims_tracker.domain.Policy;
import name.hines.steven.medical_claims_tracker.services.PolicyService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration({ "classpath:/applicationContext.xml", "classpath:/tests_persistence-applicationContext.xml" })
public class PolicyControllerTest {
@Mock
PolicyService service;
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
//this must be called for the @Mock annotations above to be processed.
MockitoAnnotations.initMocks(this);
}
@Test
public void createOrUpdateFailsWhenInvalidDataPostedAndSendsUserBackToForm() throws Exception {
//Post no parameters in this request to force errors
mockMvc.perform(post("/policies/persist")).andExpect(status().isOk())
.andExpect(model().attributeHasErrors("policy"))
.andExpect(view().name("createOrUpdatePolicy"));
}
@Test
public void createOrUpdateSuccessful() throws Exception {
//Mock the service method to force a known response
when(service.save(isA(Policy.class))).thenReturn(new Policy());
mockMvc.perform(
post("/policies/persist").param("companyName", "Company Name")
.param("name", "Name").param("effectiveDate", "2001-01-01"))
.andExpect(status().isMovedTemporarily()).andExpect(model().hasNoErrors())
.andExpect(redirectedUrl("list"));
}
}
Vous remarquerez que j'ai deux contexte fichiers de configuration; c'est un hack parce que si je suis incapable d'arrêter le test du contrôleur de frapper le réel de la couche de service alors que la couche de service pourrait aussi bien avoir ses dépôts de pointage à la base de données de test. Je ne suis pas à un point où je ne peux pas sortir avec ce hack, plus longtemps et doivent être en mesure de se moquer de ma couche de service correctement.
Pourquoi le when(service.save(isA(Policy.class))).thenReturn(new Policy());
pas de coups de pied dans et se moquant de la méthode dans le PolicyService? Ai-je raté quelques mockito de configuration quelque part? Il y a une chose que je dois mettre dans la configuration Spring? Ma recherche a été limitée à Googler "spring mvc test mockito pas de travail", mais qui ne m'a pas donné beaucoup de.
Grâce.
Mise à jour de 1
Vous avez raison @tom-verelst, je faisais allusion à la PolicyService service;
ligne dans mon test de sorte que le service à l'intérieur de la MockMvc
auront été injectés d'ici le Printemps.
J'ai fait un peu de recherche et a trouvé un post de blog qui a fait un bon travail en expliquant ce que @InjectMocks
est utilisé pour.
J'ai ensuite essayé l'annotation private MockMvc mockMvc
avec @InjectMocks
et toujours le même problème (c'est à dire le service à l'intérieur de la MockMvc
était pas moqués comme je m'attendais à être). J'ai ajouté la trace de la pile au point au cours de débogage où la méthode d'enregistrement sur le PolicyServiceImpl
est appelée (par opposition à l'souhaité appel à la méthode save dans la moqué de service).
Thread [main] (Suspended (breakpoint at line 29 in DomainEntityServiceImpl) PolicyServiceImpl(DomainEntityServiceImpl<T>).save(T) line: 29
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25
Method.invoke(Object, Object...) line: 597
AopUtils.invokeJoinpointUsingReflection(Object, Method, Object[]) line: 317
ReflectiveMethodInvocation.invokeJoinpoint() line: 183
ReflectiveMethodInvocation.proceed() line: 150
TransactionInterceptor$1.proceedWithInvocation() line: 96
TransactionInterceptor(TransactionAspectSupport).invokeWithinTransaction(Method, Class, TransactionAspectSupport$InvocationCallback) line: 260
TransactionInterceptor.invoke(MethodInvocation) line: 94
ReflectiveMethodInvocation.proceed() line: 172
JdkDynamicAopProxy.invoke(Object, Method, Object[]) line: 204
$Proxy44.save(DomainEntity) line: not available
PolicyController.createOrUpdate(Policy, BindingResult) line: 64
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25
Method.invoke(Object, Object...) line: 597
ServletInvocableHandlerMethod(InvocableHandlerMethod).invoke(Object...) line: 219
ServletInvocableHandlerMethod(InvocableHandlerMethod).invokeForRequest(NativeWebRequest, ModelAndViewContainer, Object...) line: 132
ServletInvocableHandlerMethod.invokeAndHandle(ServletWebRequest, ModelAndViewContainer, Object...) line: 104
RequestMappingHandlerAdapter.invokeHandleMethod(HttpServletRequest, HttpServletResponse, HandlerMethod) line: 746
RequestMappingHandlerAdapter.handleInternal(HttpServletRequest, HttpServletResponse, HandlerMethod) line: 687
RequestMappingHandlerAdapter(AbstractHandlerMethodAdapter).handle(HttpServletRequest, HttpServletResponse, Object) line: 80
TestDispatcherServlet(DispatcherServlet).doDispatch(HttpServletRequest, HttpServletResponse) line: 925
TestDispatcherServlet(DispatcherServlet).doService(HttpServletRequest, HttpServletResponse) line: 856
TestDispatcherServlet(FrameworkServlet).processRequest(HttpServletRequest, HttpServletResponse) line: 915
TestDispatcherServlet(FrameworkServlet).doPost(HttpServletRequest, HttpServletResponse) line: 822
TestDispatcherServlet(HttpServlet).service(HttpServletRequest, HttpServletResponse) line: 727
TestDispatcherServlet(FrameworkServlet).service(HttpServletRequest, HttpServletResponse) line: 796
TestDispatcherServlet.service(HttpServletRequest, HttpServletResponse) line: 66
TestDispatcherServlet(HttpServlet).service(ServletRequest, ServletResponse) line: 820
MockFilterChain$ServletFilterProxy.doFilter(ServletRequest, ServletResponse, FilterChain) line: 168
MockFilterChain.doFilter(ServletRequest, ServletResponse) line: 136
MockMvc.perform(RequestBuilder) line: 134
PolicyControllerTest.createOrUpdateSuccessful() line: 67
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25
Method.invoke(Object, Object...) line: 597
FrameworkMethod$1.runReflectiveCall() line: 44
FrameworkMethod$1(ReflectiveCallable).run() line: 15
FrameworkMethod.invokeExplosively(Object, Object...) line: 41
InvokeMethod.evaluate() line: 20
RunBefores.evaluate() line: 28
RunBeforeTestMethodCallbacks.evaluate() line: 74
RunAfterTestMethodCallbacks.evaluate() line: 83
SpringRepeat.evaluate() line: 72
SpringJUnit4ClassRunner.runChild(FrameworkMethod, RunNotifier) line: 231
SpringJUnit4ClassRunner.runChild(Object, RunNotifier) line: 88
ParentRunner$3.run() line: 193
ParentRunner$1.schedule(Runnable) line: 52
SpringJUnit4ClassRunner(ParentRunner<T>).runChildren(RunNotifier) line: 191
ParentRunner<T>.access$000(ParentRunner, RunNotifier) line: 42
ParentRunner$2.evaluate() line: 184
RunBeforeTestClassCallbacks.evaluate() line: 61
RunAfterTestClassCallbacks.evaluate() line: 71
SpringJUnit4ClassRunner(ParentRunner<T>).run(RunNotifier) line: 236
SpringJUnit4ClassRunner.run(RunNotifier) line: 174
JUnit4TestMethodReference(JUnit4TestReference).run(TestExecution) line: 50
TestExecution.run(ITestReference[]) line: 38
RemoteTestRunner.runTests(String[], String, TestExecution) line: 467
RemoteTestRunner.runTests(TestExecution) line: 683
RemoteTestRunner.run() line: 390
RemoteTestRunner.main(String[]) line: 197
Plus de recherches (Mockito Injecter des valeurs Null dans un Printemps bean lors de l'utilisation de @Mock?) a suggéré l'application de la @InjectMocks
à un PolicyController
variable de membre au sein de l'épreuve, mais comme le souligne l'un des réponses dans le premier lien, ce n'est rien parce que le Printemps ne sait rien à ce sujet.
Vous devez vous connecter pour publier un commentaire.
Grâce à @J Andy ligne de pensée, j'ai réalisé que j'avais été en tête dans le mauvais chemin sur cette. Dans la mise à Jour 1, j'ai essayé d'injecter de la maquette de service dans le
MockMvc
mais après avoir pris un peu de recul, j'ai réalisé que ce n'est pas leMockMvc
qui a été soumis à l'essai, il a été lePolicyController
j'ai voulu tester.Pour donner un peu de fond, je voulais éviter une unité traditionnelle de test de la @Contrôleurs dans mon Spring MVC de l'application parce que je voulais tester des choses qui sont uniquement fournies par l'exécution de contrôleurs de Printemps lui-même (par exemple, les appels RESTful pour les actions du contrôleur). Ceci peut être réalisé à l'aide de la Spring MVC framework de Test qui vous permet d'exécuter vos tests à l'intérieur de Printemps.
Vous verrez le code de ma question de départ que j'étais en train de le Spring MVC tests dans un
WebApplicationContext
(c'est à dire
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
) alors que ce que je devrait ai fait était en cours d'exécution autonome. Exécution autonome me permet d'injecter directement le contrôleur que je veux tester et, par conséquent, avoir le contrôle sur la façon dont le service est injecté dans le contrôleur (c'est à dire la force d'un simulacre de service pour les utiliser).
C'est plus facile expliqué dans le code. Donc, pour le contrôleur suivant:
J'ai maintenant le test suivant à la classe dans laquelle le service est correctement moqué et mon test de la base de données n'est plus frappé:
Je suis encore très apprentissage quand il s'agit de Ressort de telle sorte que tous les commentaires qui permettront d'améliorer mon explication serait la bienvenue. Ce blog a été utile pour moi de venir avec cette solution.
Cette section, 11.3.6 Spring MVC Framework de Test, au Printemps document 11. Les tests en parle, mais il n'est pas clair d'une certaine manière.
Continuons avec l'exemple dans le document pour l'explication. L'échantillon de test de classe ressemble comme suit
Supposons que vous avez org.exemple.AppController en tant que contrôleur. Dans le test-servlet-context.xml, vous aurez besoin d'avoir
Le document est manquant, le câblage de la partie pour le contrôleur. Et vous aurez besoin de changement à l'injection par mutateur pour accountService si vous utilisez le champ de l'injection. Également de noter que la valeur(org.exemple.AccountService ici) pour constructor-arg est une interface, pas une classe.
Dans la méthode de configuration dans AccountTests, vous aurez
La méthode d'essai peut ressembler à
andDo(print()) vient à portée de main, faire "import static org.springframework.test.web.servlet.résultat.MockMvcResultHandlers.imprimer;".
Je préfère service autonome de Mockmvc
Mentionné travail pour moi
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build()
pour obtenir leMockMvc
C'est probablement un problème avec le Printemps et Mockito de tenter à la fois d'injecter les haricots. D'une façon que je peux penser d'éviter ces problèmes est d'utiliser le Printemps ReflectionTestUtils manuellement injecter le service de fantaisie.
Dans ce cas, votre setup() méthode ressemblerait à quelque chose comme ceci
P. S. Votre convention de nommage sont peu hors, à mon humble avis et je suis en supposant que mockMvc est la classe que vous essayez de test (SAT). J'aimerais utiliser les noms suivants au lieu
java.lang.IllegalArgumentException: Could not find field [service] of type [null] on target [[email protected]]
Le fait que vous avez supposé que c'était lamockMvc
objet de la classe sous test m'a fait prendre du recul et de regarder une autre façon d'obtenir ce que je voulais faire (et dont le résultat peut être vu dans ma propre réponse à la question). Alors je vous remercie beaucoup d'avoir pris le temps et la peine de m'aider à explorer mon problème et en fin de compte me diriger vers la bonne solution.setfield()
méthode. Et ne le mockMvc contiennent même un service, comme vous le dites c'est le contrôleur est votre UET? De toute façon, je vois que tu as résolu le problème à l'origine, de la mode, ce qui est une meilleure approche.Vous êtes à la création d'une maquette pour
PolicyService
, mais vous n'êtes pas à l'injecter dans votreMockMvc
aussi loin que je peux dire. Cela signifie que lePolicyService
défini dans votre configuration Spring sera appelée à la place de votre maquette.Soit injecter de la maquette de la
PolicyService
dans votreMockMvc
par la valeur, ou de prendre un coup d'oeil à Springockito pour l'injection des simulacres.MockitoAnnotations.initMocks(this);
dans le@Before
fait que l'injection de la se moque de moi.service
a été mis àPolicyService$$EnhancerByMockitoWithCGLIB$$a8cf961b
. Cependant, j'ai aussi mis un point d'arrêt dans la méthode save de la classe de Service et qui a été touché aussi. C'est presque comme si le Printemps est l'injection de service après Mockito a sont moqués. Est Springockito la seule façon de contourner ce comportement?MockMvc
? L'attribut de service dans votre unité de test sera certainement une référence àPolicyService$$EnhancerByMockitoWithCGLIB$$...
, mais votre attribut de service à l'intérieur de laMockMvc
ne sera pas. Si vous utilisez@InjectMocks
comme suggéré par @J Andy, la maquette sera injecté dans votreMockMvc
MockMvc
? Est ma prochaine étape, essayer d'utiliser l'injection de dépendance par l'intermédiaire d'un test spécifique à la demande de config XML? Ce serait une honte que je voudrais vraiment l'élégance de l'injection deMockMvc
dans le code de test.