如何使用Mockito模拟一个带有@InjectMock注释的对象



我想模拟服务来对我的spring应用程序中的Controller层进行单元测试。而控制器中有一些方法需要进行mock。即控制器类别如下:

@Controller 
@RequestMapping("/execution-unit")
public class ExecutionUnitController {
    @Resource
    private IRoleService roleService;
    @RequestMapping("/list")
    public ModelAndView list(HttpServletRequest request) {
        ModelAndView view = new ModelAndView("execution-unit/list");
        User user = this.getUser();
        view.addObject("user", user);
        // if the operator has media role
        if (user != null) {
            if (roleService.ifUserHasRole(user.getId(), RoleType.MEDIA)
                    || roleService.ifUserHasRole(user.getId(), RoleType.MEDIALLEADER)) {
                view.addObject("isMedia", true);
            }
        }
        return view;
    }
    // get the current user
    public User getUser() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if(authentication!=null){
            Object obj = authentication.getPrincipal();
            if (obj instanceof User) {
                return (User) obj;
            }
        }
        return null;
    }
}

测试单元如下:

@ContextConfiguration(locations = {"classpath:testApplicationContext.xml"})
@RunWith(SpringJUnit4ClassRunner.class)
@Transactional
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
@WebAppConfiguration
public class ExecutionUnitControllerTest {
    @Mock
    private IRoleService roleService;
    @InjectMocks
    @Resource
    private ExecutionUnitController executionUnitController;
    private MockMvc mockMvc;
    @Autowired
    private WebApplicationContext webApplicationContext;
    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }
    @Test
    public void testList() throws Exception {
        User user = new User();
        user.setId(2);
        when(executionUnitController.getUser()).thenReturn(user); // this line throw exception
        when(roleService.ifUserHasRole(user.getId(), Role.RoleType.MEDIA)).thenReturn(true);
        this.mockMvc.perform(get("/execution-unit/list"))
                .andExpect(status().isOk())
                .andExpect(forwardedUrl("/jsp/execution-unit/list.jsp"))
                .andExpect(model().attribute("isMedia", true))
                .andExpect(model().attribute("user", user));
    }
}

但是,测试单元的结果是抛出异常,异常信息如下:

2013-11-27 11:48:06,240 INFO [org.springframework.test.context.transaction.TransactionalTestExecutionListener] - <Rolled back transaction after test execution for test context [TestContext@385715 testClass = ExecutionUnitControllerTest, testInstance = com.sohu.tv.crm.contoller.ExecutionUnitControllerTest@72dd23cf, testMethod = testList@ExecutionUnitControllerTest, testException = org.mockito.exceptions.misusing.MissingMethodInvocationException: 
when() requires an argument which has to be 'a method call on a mock'.
For example:
    when(mock.getArticles()).thenReturn(articles);
Also, this error might show up because:
1. you stub either of: final/private/equals()/hashCode() methods.
   Those methods *cannot* be stubbed/verified.
2. inside when() you don't call method on mock but on some other object., mergedContextConfiguration = [WebMergedContextConfiguration@145a25f3 testClass = ExecutionUnitControllerTest, locations = '{classpath:testApplicationContext.xml, classpath:testActiviti.cfg.xml}', classes = '{}', contextInitializerClasses = '[]', activeProfiles = '{}', resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.test.context.web.WebDelegatingSmartContextLoader', parent = [null]]]>
org.mockito.exceptions.misusing.MissingMethodInvocationException: 
when() requires an argument which has to be 'a method call on a mock'.
For example:
    when(mock.getArticles()).thenReturn(articles);
Also, this error might show up because:
1. you stub either of: final/private/equals()/hashCode() methods.
   Those methods *cannot* be stubbed/verified.
2. inside when() you don't call method on mock but on some other object.
    at com.sohu.tv.crm.contoller.ExecutionUnitControllerTest.testList(ExecutionUnitControllerTest.java:84)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:231)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:88)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:174)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:76)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:195)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:63)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

我曾尝试使用@Spy注释:

@Spy
private ExecutionUnitController blogController;

它还抛出了一个异常:

org.mockito.exceptions.base.MockitoException: Cannot create a @Spy for 'executionUnitController' field because the *instance* is missing
The instance must be created *before* initMocks();
Example of correct usage of @Spy:
   @Spy List mock = new LinkedList();
   //also, don't forget about MockitoAnnotations.initMocks();

可能我没有添加@Resource注释,所以我在ExecutionUnitController之前添加了它,但roleService和ExecutionUnitController并没有使用我预期的mock方式,它调用了real方法。

Mockito有一个名为spy的功能(请参阅注释),它可以用于偏袒嘲讽。重要的是,您使用do-when sytnax,而不是when-then。

@Spy
private ExecutionUnitController executionUnitController;
...
doReturnuser).when(executionUnitController).getUser();

使用mocking框架*,您只能用mock替换一个完整的类(及其所有方法)。但是,在执行普通代码的类中,不能只替换单个方法。

这意味着,您不能在同一测试中运行ExecutionUnitController.list(HttpServletRequest)的代码并替换方法ExecutionUnitController.getUser()

解决方法是

  • 在测试中显式的安全上下文持有者SecurityContextHolder.setContext(SecurityContext)中设置prinicpal
  • 在新服务(CurrentUserService)中移动方法getUser(),然后可以模拟它

*我所知道的所有嘲讽框架

相关内容

  • 没有找到相关文章

最新更新