当前位置: 首页 > 面试题库 >

无法在Spring MVC Controller测试中模拟Service类

寿浩言
2023-03-14
问题内容

我有一个Spring 3.2 MVC应用程序,并且正在使用Spring MVC测试框架来测试控制器操作上的GET和POST请求。我正在使用Mockito模拟服务,但是发现模拟被忽略并且正在使用我的实际服务层(因此,数据库被命中)。

我的Controller测试中的代码:

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

你会注意到我有两个上下文配置文件;这是一个黑客,因为如果我无法阻止控制器测试到达实际的服务层,则该服务层也可能会将其存储库指向测试数据库。我已经到了无法再摆脱这种黑客攻击的地步,需要能够正确地模拟我的服务层。

为什么在when(service.save(isA(Policy.class))).thenReturn(new Policy());PolicyService中不加入并模拟出save方法?我是否在某处缺少某些Mockito配置?我需要在Spring配置中添加一些内容吗?到目前为止,我的研究仅限于谷歌搜索“ spring mvc test Mockito不起作用”,但这并没有给我带来太多帮助。

谢谢。

更新1
你是正确的@ tom-verelst,我指的是PolicyService service;测试中的那行,因此,MockMvc遗嘱中的服务当然已由Spring注入。

然后,我尝试标注private MockMvc mockMvc@InjectMocks和仍然得到了同样的问题(即内部的服务MockMvc是不是因为我是期待它是嘲笑)。我已经在调试过程PolicyServiceImpl中调用上的save方法的位置添加了堆栈跟踪(这与模拟服务中对save方法的期望调用相反)。

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   

进一步的研究(使用@Mock时将Mockito注入Null值到Spring bean中?)建议在测试中将其@InjectMocks应用于PolicyController成员变量,但是正如第一个链接中的一个答案所指出的那样,此操作无济于事,因为Spring没有一无所知。


问题答案:

感谢@J Andy的思路,我意识到我一直在走这条路。在Update
1中,我试图将模拟服务注入,MockMvc但是在退后一步后,我意识到并不是MockMvc要测试的是,而是PolicyController我要测试的。

为了提供一些背景知识,我想避免在Spring
MVC应用程序中对@Controllers进行传统的单元测试,因为我想测试仅通过在Spring自身内部运行控制器才能提供的功能(例如,对控制器操作的RESTful调用)。这可以通过使用Spring
MVC Test框架来实现,该框架允许您在Spring中运行测试。

您可以从代码在我最初的问题看,我是运行在一个春天MVC测试WebApplicationContext (即this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); 而我) 应该
已经做了独立运行。独立运行使我可以直接注入要测试的控制器,因此可以控制将服务注入控制器的方式(即强制使用模拟服务)。

这在代码中更容易解释。因此对于以下控制器:

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

现在,我具有以下测试类,其中的服务已成功被模拟出来,并且我的测试数据库不再受命:

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

关于Spring方面,我仍然非常学习,因此欢迎任何可以改善我的解释的评论很有帮助。



 类似资料:
  • 我正在使用Spring boot和Mockito进行测试。我已经能够为服务层编写测试用例,它们工作得很好。但是,针对DAO层的测试用例却没有。在执行测试用例时,被mocked和autowired的对象提供指针。以下是详细情况: 我的类: java:

  • 我正在编写单元测试的方法,以找到银行附近我的位置。我嘲弄了这个类并尝试调用这些方法。但是,控件不会转到方法来执行它。下面是单元测试用例。 我所尝试的也是调用它的真实方法, 这调用真实的方法,但我上面嘲笑的方法执行起来就像真实的方法一样。意思是“被嘲弄的方法”没有返回我要求它们返回的内容。 那么,我在这里做错了什么?方法为什么不执行?

  • 我有一个Spring 3.2 MVC应用程序,正在使用Spring MVC测试框架测试控制器动作的GET和POST请求。我使用Mockito来模拟服务,但我发现模拟被忽略了,我的实际服务层被使用了(因此,数据库被击中)。 控制器测试中的代码: 你会注意到我有两个上下文配置文件;这是一种黑客行为,因为如果我无法阻止控制器测试命中实际的服务层,那么该服务层的存储库也可能指向测试数据库。我再也不能忍受这

  • 在我的代码中,我有时在同一个类中调用public或private方法。这些方法并不是一个很好的候选方法,不适合被拉入自己的类中。我调用的每个方法都在它们自己的单元测试中进行测试。 那么,如果我的类a中有一个方法也在类a中调用这些方法中的每一个,有没有办法模拟这些调用?我当然可以剪切和粘贴我的期望/模拟行为,但这不仅是一件乏味的事情,它混淆了测试的要点,违反了模块化,并且由于无法控制返回的内容,使测

  • 在我的代码中,我有时在同一个类中调用公共或私有方法。这些方法不适合被拉到自己的类中。我调用的这些方法中的每一个都是在它们自己的单元测试中进行测试的。 那么,如果我的类a中有一个方法,它也调用类a中的每个方法,那么有什么方法可以模拟这些调用吗?我当然可以剪切和粘贴我的期望/模拟行为,但这不仅是乏味的,它混淆了测试的重点,违反了模块化,并且由于无法控制返回的内容而使测试更加困难。 如果不是,这种事情通

  • redis-cli -p 6379 DEBUG sleep 30