Reputation: 41
I want to mock Service to unit test the Controller layer in my spring application. while there are some method in the controller need to be mock. i.e. An controller class is as following:
@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;
}
}
The Test Unit is as following:
@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));
}
}
But, the test unit's result is throw an exception, the exception infomation is as following:
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)
And I had tried to use @Spy annotation:
@Spy
private ExecutionUnitController blogController;
it also throw an exception:
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();
may be i don't add the @Resource annotation, so i add it before ExecutionUnitController,but the roleService and executionUnitController don't acted using the mock way i expected, it called the real method.
Upvotes: 4
Views: 4566
Reputation: 120761
Mockito has a feature called spy (see comment), it can be used for partiality mocking. The important thing is that you use the do-when sytnax, instead of when-then.
@Spy
private ExecutionUnitController executionUnitController;
...
doReturnuser).when(executionUnitController).getUser();
With mocking frameworks* you can replace only a complete class (with all its methods) by a mock. But you can not replace only a single method in on class where you execute normal code.
This mean, you can not run the code of ExecutionUnitController.list(HttpServletRequest)
and replace the method ExecutionUnitController.getUser()
in the same test.
The workarounds are
SecurityContextHolder.setContext(SecurityContext)
getUser()
in an new Service (CurrentUserService), then you can mock it* all mocking frameworks I know so fare
Upvotes: 1