如何使用Mockito模拟注释为@InjectMock的对象

时间:2013-11-27 07:27:19

标签: java spring unit-testing mockito

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

@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没有使用我预期的模拟方式,它称为真实方法。

1 个答案:

答案 0 :(得分:0)

Mockito有一个名为spy的功能(请参阅注释),它可用于偏好模拟。重要的是你使用do-when sytnax而不是when-then。

@Spy
private ExecutionUnitController executionUnitController;

...

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

<击> 使用模拟框架*,您只能通过模拟替换完整的类(及其所有方法)。但是你不能只替换执行普通代码的类中的单个方法。

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

解决方法是

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