Issue
I want to write unit test cases for the following spring MVC controller using mockito and powermockito.
@Controller
@Configuration
@PropertySource("classpath:project/web/properties/RealTimeAPI.properties")
@RequestMapping("/learnon")
public class ClassManagerController {
private final Logger logger = Logger.getLogger(ClassManagerController.class);
@Autowired
private ClassManagerService classManagerService;
@Autowired
private GroupUserService groupUserService;
@RequestMapping(value = "/teacher", method = RequestMethod.GET)
public ModelAndView showClassDetail(HttpServletRequest request, HttpSession httpSession,
@RequestParam(value = "isbn", required = false) String isbn13,
@RequestParam(value = "classId", required = false) Long classId) {
String redirectUrl = "https://example.com/jsp/Login.jsp?reason=failedLogin&redirectUri=https://example.com/secure/Bookshelf";
String accessDeniedUri = "https://example.com/jsp/AccessDenied.jsp";
if (httpSession.getAttribute("USERID") == null) {
return new ModelAndView("redirect:" + redirectUrl);
}
try {
long userId = Long.parseLong(httpSession.getAttribute("USERID").toString());
UserBean user = classManagerService.getUser(userId);
if (httpSession.getAttribute("SCHOOLID") == null) {
httpSession.setAttribute("SCHOOLID", user.getSchoolId());
}
if (httpSession.getAttribute("FULLFILLMENT_YEAR") == null) {
httpSession.setAttribute("FULLFILLMENT_YEAR", user.getFulfillmentYear());
}
String isbn10 = ISBNUtil.convertIsbn13ToIsbn10(isbn13);
String title = "";
ModelAndView mav = null;
ClassManagerBean classBean = null;
if(classId == null && httpSession.getAttribute("classId") != null){
classId = (Long)httpSession.getAttribute("classId");
}
if(classId != null && classId > 0) {
List<UserBean> userBeanList = classManagerService.getUserList(user.getSchoolId(), classId, isbn10);
classBean = classManagerService.getClassById(classId);
classBean.setUserNumber(userBeanList.size());
title = classBean.getTitle();
//Set the view to ClassManager.jsp
mav = new ModelAndView("ClassManager");
mav.addObject("userList", userBeanList);
boolean authorized = userBeanList.stream().anyMatch(u->u.getUserId() == userId);
if(!authorized){
ModelAndView modelAndView = new ModelAndView("redirect:" + accessDeniedUri);
modelAndView.addObject("accessDenied", "true");
return modelAndView;
}
}else{
title = classManagerService.getTitle(isbn10);
//Set the view to createNewClass.jsp
mav = new ModelAndView("CreateNewClass");
classBean = new ClassManagerBean();
classBean.setLo2Flag(true);
classBean.setIsbn(isbn10);
classBean.setTitle(title);
}
httpSession.setAttribute("searchTitle", title);
httpSession.setAttribute("selectedIsbn", isbn10);
httpSession.setAttribute("classId", classId);
mav.addObject("user", user);
mav.addObject("classBean", classBean);
return mav;
} catch (Exception ex) {
ModelAndView mav2 = new ModelAndView("redirect:" + accessDeniedUri);
mav2.addObject("accessDenied", "true");
logger.error("Exception Occurred, Redirecting to Access Denied...", ex);
return mav2;
}
}
}
I have written the following unit test case for the above class and I'm getting an UnfinishedStubbingException exception at runtime when I try to run the test.
@Test
public void testShowClassDetail1() throws Exception {
HttpServletRequest httpRequest = mock(HttpServletRequest.class);
HttpSession httpSession = mock(HttpSession.class);
Mockito.when(httpSession.getAttribute("USERID")).thenReturn(new String("anyString"));
List<UserBean> list = new ArrayList<UserBean>();
List<UserBean> spyList = Mockito.spy(list);
Mockito.when(classManagerService.getUserList(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString())).thenReturn(spyList);
doReturn(false).when(spyList.stream().anyMatch(u->u.getUserId() == Mockito.anyLong()));
RequestBuilder request = MockMvcRequestBuilders
.get("/learnon/teacher")
.param("isbn", "1234567890123")
.param("classId", "1")
.accept(MediaType.APPLICATION_JSON);
String modalView = "redirect:" + "https://www.example.com/jsp/AccessDenied.jsp";
ResultActions result = mockMvc.perform(request)
.andExpect(status().is3xxRedirection())
.andExpect(view().name(modalView));
}
Exception:
org.mockito.exceptions.misusing.UnfinishedStubbingException:
Unfinished stubbing detected here:
-> at learnonclassmanager.spring.web.controller.ClassManagerControllerTest.testShowClassDetail1(ClassManagerControllerTest.java:98)
E.g. thenReturn() may be missing.
Examples of correct stubbing:
when(mock.isOk()).thenReturn(true);
when(mock.isOk()).thenThrow(exception);
doThrow(exception).when(mock).someVoidMethod();
Hints:
1. missing thenReturn()
2. you are trying to stub a final method, you naughty developer!
3: you are stubbing the behaviour of another mock inside before 'thenReturn' instruction if completed
at learnonclassmanager.spring.web.controller.ClassManagerControllerTest.testShowClassDetail1(ClassManagerControllerTest.java:98)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:68)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:316)
at org.junit.internal.runners.MethodRoadie$2.run(MethodRoadie.java:89)
at org.junit.internal.runners.MethodRoadie.runBeforesThenTestThenAfters(MethodRoadie.java:97)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.executeTest(PowerMockJUnit44RunnerDelegateImpl.java:300)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.executeTestInSuper(PowerMockJUnit47RunnerDelegateImpl.java:131)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.access$100(PowerMockJUnit47RunnerDelegateImpl.java:59)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner$TestExecutorStatement.evaluate(PowerMockJUnit47RunnerDelegateImpl.java:147)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.evaluateStatement(PowerMockJUnit47RunnerDelegateImpl.java:107)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.executeTest(PowerMockJUnit47RunnerDelegateImpl.java:82)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runBeforesThenTestThenAfters(PowerMockJUnit44RunnerDelegateImpl.java:288)
at org.junit.internal.runners.MethodRoadie.runTest(MethodRoadie.java:87)
at org.junit.internal.runners.MethodRoadie.run(MethodRoadie.java:50)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.invokeTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:208)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.runMethods(PowerMockJUnit44RunnerDelegateImpl.java:147)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$1.run(PowerMockJUnit44RunnerDelegateImpl.java:121)
at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:34)
at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:44)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.run(PowerMockJUnit44RunnerDelegateImpl.java:123)
at org.powermock.modules.junit4.common.internal.impl.JUnit4TestSuiteChunkerImpl.run(JUnit4TestSuiteChunkerImpl.java:121)
at org.powermock.modules.junit4.common.internal.impl.AbstractCommonPowerMockRunner.run(AbstractCommonPowerMockRunner.java:53)
at org.powermock.modules.junit4.PowerMockRunner.run(PowerMockRunner.java:59)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:89)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:541)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:763)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:463)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:209)
I tried to resolve the exception but still no luck. Help me to resolve this exception.
Edit:
I removed the following line doReturn(false).when(spyList.stream().anyMatch(u->u.getUserId() == Mockito.anyLong()));
and update the test as follow.
@Test
public void testShowClassDetail1() throws Exception {
HttpServletRequest httpRequest = mock(HttpServletRequest.class);
HttpSession httpSession = mock(HttpSession.class);
Mockito.when(httpSession.getAttribute("USERID")).thenReturn(1l);
UserBean beanMock = mock(UserBean.class);
Mockito.when(classManagerService.getUser(1l)).thenReturn(beanMock);
List<UserBean> beanList = new ArrayList<>();
beanList.add(beanMock);
Mockito.when(classManagerService.getUserList(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString())).thenReturn(beanList);
Mockito.when(beanMock.getUserId()).thenReturn(2l);
RequestBuilder request = MockMvcRequestBuilders
.get("/learnon/teacher")
.param("isbn", "1234567890123")
.param("classId", "1")
.accept(MediaType.APPLICATION_JSON);
String modalView = "redirect:" + "https://example.com/jsp/AccessDenied.jsp";
ResultActions result = mockMvc.perform(request)
.andExpect(status().is3xxRedirection())
.andExpect(view().name(modalView));
}
now i am getting following AssetionError.
java.lang.AssertionError: View name expected:https://example.com/jsp/AccessDenied.jsp> but was:https://example.com/jsp/Login.jsp?reason=failedLogin&redirectUri=https:/
Solution
Let's look at the following line (formatted for legibility):
doReturn(false).when(
spyList.stream()
.anyMatch(u->u.getUserId() == Mockito.anyLong())
);
It contains two errors, one is reported by the exception, but I'll also explain the other
Error 1: Method call on real object passed to when
In order to record the method call, you must pass the result of a method call on a mock to when function. This is not the case in your code, as stream()
returns real object
See How does mockito when() invocation work?
Error 2: Invalid use of ArgumentMatchers
You cannot use Mockito.anyLong()
as an arbitrary value. This compiles, as ArgumentMatchers return dummy value (zero), but does not work as intended (comparison with anyLong() is not always true).
See How do Mockito matchers work?
Solution
The goal of mocking is to force certain conditions to be met in your method under test. Your goal is to return false when looking for users by id. To achieve it, simply use empty list. This means that entire problematic line can be deleted.
Answered By - Lesiak
Answer Checked By - Terry (JavaFixing Volunteer)