【问题标题】:How to mock an object which with annnotation of @InjectMock using Mockito如何使用 Mockito 模拟带有 @InjectMock 注释的对象
【发布时间】:2013-12-12 17:30:31
【问题描述】:

我想模拟 Service 以在我的 Spring 应用程序中对 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没有使用我期望的模拟方式,它调用了真正的方法。

【问题讨论】:

  • 阅读 Mockito 间谍。它们将是您的问题的一种解决方案。如果您不知道该怎么做,请发表评论,如果有时间,我会写一个完整的解决方案。
  • 我尝试使用@Spy 注解,但 roleService 是真正的方法,而不是模拟。
  • 好的,我正在睡觉的路上,但上帝保佑我明天某个时间会发布解决方案。

标签: java spring unit-testing mockito


【解决方案1】:

Mockito 有一个名为spy 的功能(见评论),它可以用于部分模拟。重要的是您使用 do-when 语法,而不是 when-then。

@Spy
private ExecutionUnitController executionUnitController;

...

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

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

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

解决方法是

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

【讨论】:

  • 我不确定你声称知道哪些模拟框架,但 Mockito 肯定不是这样,它是用于标记问题的模拟框架。
  • @DavidWallace,你的意思是“间谍”功能吗?
  • 不,我实际上是指“默认答案”功能 - CALLS_REAL_METHODS 如果我没记错的话。但是使用间谍显然也有效 - 正如我不久前在问题下评论的那样。
  • 我试过spy annotation,但它不能解决这个问题,@David Wallace,我使用mockito框架。
猜你喜欢
  • 2014-02-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-07
  • 2017-10-11
相关资源
最近更新 更多