我想模拟服务来对我的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()
,然后可以模拟它
*我所知道的所有嘲讽框架