如何使用Mockito模拟带有@InjectMock注解的对象

4

我希望通过模拟服务来单元测试Spring应用程序中的控制器层。虽然控制器中有一些需要进行模拟的方法。例如,以下是一个控制器类:

@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并没有按照我预期的模拟方式进行操作,而是调用了真实的方法。


阅读有关Mockito间谍的内容。它们可能是解决您问题的一个方案。如果您无法弄清楚如何实现,请在评论中留言,如果我有时间,我会写出完整的解决方案。 - Dawood ibn Kareem
我尝试使用@Spy注解,但是roleService执行了真实的方法,而不是模拟的方法。 - mushui
好的,我现在要去睡觉了,但是若是上帝愿意,明天我会发布一个解决方案。 - Dawood ibn Kareem
1个回答

1

Mockito有一个称作spy(见注释)的功能,它可以用于部分模拟。重要的是你使用do-when语法,而不是when-then语法。

@Spy
private ExecutionUnitController executionUnitController;

...

doReturnuser).when(executionUnitController).getUser();

使用模拟框架*,您可以仅用模拟替换整个类(及其所有方法)。但是,您不能仅在执行普通代码的一个类中替换单个方法。
这意味着,您无法在同一测试中运行ExecutionUnitController.list(HttpServletRequest)代码并替换ExecutionUnitController.getUser()方法。
解决方法如下:

*我所知道的所有模拟框架


我不确定你声称了解哪些模拟框架,但这绝对不适用于 Mockito,因为问题已经被标记为使用该模拟框架。 - Dawood ibn Kareem
@DavidWallace,您是指“spy”功能吗? - Ralph
不,我实际上是指“默认答案”功能 - 如果我没记错的话,应该是CALLS_REAL_METHODS。但使用间谍也可以 - 正如我之前在问题下面评论的那样。 - Dawood ibn Kareem
1
我曾尝试过使用Spy注解,但它无法解决这个问题。@David Wallace,我使用Mockito框架。 - mushui
我使用了:(at)Spy (at)InjectMock MyClass myclass = new MyClass(); 我必须提供它的实例才能让它工作。 - Steve T

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接