Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unable to mock Service class in Spring MVC Controller tests

I have a Spring 3.2 MVC application and am using the Spring MVC test framework to test GET and POST requests on the actions of my controllers. I am using Mockito to mock the Services but am finding that the mocks are being ignored and that my actual Service layer is being used (and, as a consequence, the database is being hit).

The code in my Controller test:

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"));     } } 

You'll notice I have two context configuration files; this is a hack because if I'm unable to stop the controller test hitting the actual service layer then that service layer might as well have its repositories pointing at the test database. I'm not at a point where I can't get away with this hack any longer and need to be able to mock out my service layer properly.

Why is the when(service.save(isA(Policy.class))).thenReturn(new Policy()); not kicking in and mocking out the save method in the PolicyService? Am I missing some mockito configuration somewhere? Is there something I need to put in the Spring configuration? My reasearch so far has been limited to Googling "spring mvc test mockito not working" but that has not given me much to go on.

Thanks.


Update 1

You were right @tom-verelst, I was referring to the PolicyService service; line in my test so the service inside the MockMvc will of course have been injected by Spring.

I did a bit of research and found a blog post which did a good job of explaining what @InjectMocks is used for.

I then tried annotating private MockMvc mockMvc with @InjectMocks and still got the same problem (i.e. the service inside the MockMvc was not mocked as I was expecting it to be). I have added the stack trace at the point during debugging where the save method on the PolicyServiceImpl is called (as opposed to the desired call to the save method in the mocked 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    

More research (Mockito Injecting Null values into a Spring bean when using @Mock?) suggested applying the @InjectMocks to a PolicyController member variable within the test, but as pointed out in one of the answers in the first link, this does nothing because Spring doesn't know anything about it.

like image 429
stevenghines Avatar asked Apr 23 '13 13:04

stevenghines


1 Answers

Thanks to @J Andy's line of thought, I realised that I had been heading down the wrong path on this. In Update 1 I was trying to inject the mock service into the MockMvc but after taking a step back I realised that it's not the MockMvc that was under test, it was the PolicyController I wanted to test.

To give a bit of background, I wanted to avoid a traditional unit test of the @Controllers in my Spring MVC application because I wanted to test things that are only provided by running the controllers within Spring itself (e.g. RESTful calls to controller actions). This can be achieved by using the Spring MVC Test framework which allows you to run your tests within Spring.

You'll see from the code in my initial question that I was running the Spring MVC tests in a WebApplicationContext (i.e. this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); ) whereas what I should have been doing was running standalone. Running standalone allows me to directly inject the controller I want to test and, therefore, have control over how the service is injected into the controller (i.e. force a mock service to be used).

This is easier explained in code. So for the following controller:

import javax.validation.Valid;  import name.hines.steven.medical_claims_tracker.domain.Benefit; import name.hines.steven.medical_claims_tracker.domain.Policy; import name.hines.steven.medical_claims_tracker.services.DomainEntityService; import name.hines.steven.medical_claims_tracker.services.PolicyService;  import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.ModelAndView;  @Controller @RequestMapping("/policies") public class PolicyController extends DomainEntityController<Policy> {      @Autowired     private PolicyService service;      @RequestMapping(value = "persist", method = RequestMethod.POST)     public String createOrUpdate(@Valid @ModelAttribute("policy") Policy policy, BindingResult result) {         if (result.hasErrors()) {             return "createOrUpdatePolicyForm";         }         service.save(policy);         return "redirect:list";     } } 

I now have the following test class in which the service is successfully mocked out and my test database is no longer hit:

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.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders;  @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration({ "classpath:/applicationContext.xml" }) public class PolicyControllerTest {      @Mock     PolicyService policyService;      @InjectMocks     PolicyController controllerUnderTest;      private MockMvc mockMvc;      @Before     public void setup() {          // this must be called for the @Mock annotations above to be processed         // and for the mock service to be injected into the controller under         // test.         MockitoAnnotations.initMocks(this);          this.mockMvc = MockMvcBuilders.standaloneSetup(controllerUnderTest).build();      }      @Test     public void createOrUpdateFailsWhenInvalidDataPostedAndSendsUserBackToForm() throws Exception {         // POST no data to the form (i.e. an invalid POST)         mockMvc.perform(post("/policies/persist")).andExpect(status().isOk())         .andExpect(model().attributeHasErrors("policy"))         .andExpect(view().name("createOrUpdatePolicy"));     }      @Test     public void createOrUpdateSuccessful() throws Exception {          when(policyService.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"));     } } 

I'm still very much learning when it comes to Spring so any comments that will improve my explanation would be welcomed. This blog post was helpful to me in coming up with this solution.

like image 80
stevenghines Avatar answered Oct 14 '22 17:10

stevenghines