58 votes

Impossible de simuler la classe Service dans les tests du contrôleur Spring MVC

J'ai une application Spring 3.2 MVC et j'utilise le framework de test Spring MVC pour tester les requêtes GET et POST sur les actions de mes contrôleurs. J'utilise Mockito pour simuler les services, mais je constate que les simulations sont ignorées et que ma couche de service réelle est utilisée (et, par conséquent, la base de données est touchée).

Le code de mon test de 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 fichiers de configuration de contexte ; c'est une astuce car si je ne peux pas empêcher le test du contrôleur de toucher la couche de service réelle, alors cette couche de service pourrait tout aussi bien avoir ses référentiels pointant vers la base de données de test. Je ne suis pas arrivé à un point où je ne peux plus m'en sortir avec ce hack et j'ai besoin d'être capable de simuler ma couche de service correctement.

Pourquoi la when(service.save(isA(Policy.class))).thenReturn(new Policy()); n'intervient pas et ne se moque pas de la méthode save dans le PolicyService ? Est-ce qu'il me manque une configuration de mockito quelque part ? Est-ce qu'il y a quelque chose que je dois mettre dans la configuration de Spring ? Pour l'instant, mes recherches se sont limitées à Googler "spring mvc test mockito not working" mais cela ne m'a pas donné beaucoup d'éléments pour avancer.

Merci.


Mise à jour 1

Vous aviez raison @tom-verelst, je faisais référence à la PolicyService service; dans mon test pour que le service à l'intérieur du MockMvc aura bien sûr été injecté par Spring.

J'ai fait quelques recherches et j'ai trouvé un article de blog qui a fait un bon travail d'explication de ce qu'est un @InjectMocks est utilisé pour.

J'ai ensuite essayé d'annoter private MockMvc mockMvc con @InjectMocks et j'ai toujours le même problème (c'est-à-dire que le service à l'intérieur du fichier MockMvc était no comme je m'y attendais). J'ai ajouté la trace de pile au moment du débogage où la méthode de sauvegarde sur le fichier PolicyServiceImpl est appelée (par opposition à l'appel souhaité à la méthode save dans le service simulé).

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 recherche ( Mockito Injecter des valeurs Null dans un Spring bean lors de l'utilisation de @Mock ? ) a suggéré d'appliquer la @InjectMocks à un PolicyController dans le test, mais comme indiqué dans l'une des réponses du premier lien, cela ne fait rien parce que Spring ne sait rien à ce sujet.

3voto

Krushna Points 705

Il existe une autre solution avec la dernière version de Spring qui utilise @WebMvcTest. Exemple ci-dessous.

@RunWith(SpringRunner.class)
@WebMvcTest(CategoryAPI.class)
public class CategoryAPITest {

@Autowired
private MockMvc mvc;

@MockBean
CategoryAPIService categoryAPIService;

@SpyBean
Utility utility;

PcmResponseBean responseBean;

@Before
public void before() {
    PcmResponseBean responseBean = new PcmResponseBean("123", "200", null, null);
    BDDMockito.given(categoryAPIService.saveCategory(anyString())).willReturn(responseBean);
}

@Test
public void saveCategoryTest() throws Exception {
    String category = "{}";
    mvc.perform(post("/api/category/").content(category).contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk()).andExpect(jsonPath("messageId", Matchers.is("123")))
            .andExpect(jsonPath("status", Matchers.is("200")));
  }

}

Ici, nous ne chargeons que la classe CategoryAPI, qui est une classe de contrôleur Spring Rest, et toutes les autres sont des simulacres. Spring a sa propre version d'annotation comme @MockBean et @SpyBean similaire à mockito @Mock et @Spy.

2voto

Tom Verelst Points 1767

Vous créez un simulacre pour PolicyService mais vous ne l'injectez pas dans votre MockMvc pour autant que je sache. Cela signifie que le PolicyService définie dans votre configuration Spring sera appelée à la place de votre mock.

Soit injecter l'imitation de la PolicyService dans votre MockMvc en le paramétrant, ou consultez le site Springockito pour injecter des mocks.

0voto

jack-101 Points 1

Solution alternative

  1. lors de l'utilisation de WebApplicationContext, il trouvera les dépendances dans le conteneur Spring. Ainsi, si nous voulons simuler quelque chose, nous devons le faire dans le conteneur Spring, et c'est votre fichier de configuration du contexte.

    @Bean public FileResourceService fileResourceService() { FileResourceServiceImpl service = Mockito.mock(FileResourceServiceImpl.class) ; return service ; }

  2. lorsque vous utilisez cette annotation Mock dans la classe de test, cela n'a aucun sens pour le conteneur Spring. Il ne sait rien de tout cela.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X