0

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:/

Niranga Sandaruwan
  • 691
  • 2
  • 19
  • 39

3 Answers3

1

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.

Lesiak
  • 22,088
  • 2
  • 41
  • 65
  • i removed the error line.and updated the test.but i am getting assertion error now.can you look into the edit of the question.let me know why test is failing – Niranga Sandaruwan May 27 '20 at 08:30
  • If you need your `boolean authorized` to be true in the test, you need to have a UserBean with userId equal to userId that you provide in your session (in your test it is 1L). Simply add such a userBean to `list` created in your test (If it is difficult to create a UserBean, add a mock with `getUserId` returning 1L) – Lesiak May 27 '20 at 13:18
  • Thank you @Lesiak .I updated the test and added as a answer – Niranga Sandaruwan May 28 '20 at 05:49
1
@RunWith(PowerMockRunner.class)
public class ClassManagerControllerTest {

    @Mock
    public ClassManagerService classManagerService;

    @InjectMocks
    public ClassManagerServiceImpl classManagerServiceImpl;

    @Mock
    public GroupUserService groupUserService;

    private MockMvc mockMvc;

    @InjectMocks
    private ClassManagerController classManagerController;

    @Before
    public void setUp()  {

        mockMvc = MockMvcBuilders.standaloneSetup(classManagerController).build();
        MockitoAnnotations.initMocks(this);
    }





@Test
    public void testShowClassDetail1() throws Exception {

        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")  
                .sessionAttr("USERID", 1l)
                .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));

       }

}
Niranga Sandaruwan
  • 691
  • 2
  • 19
  • 39
  • Looks good. I would suggest: 1. rename your variables (`beanMock` -> `userMock`, `beanList` -> `userList` etc). 2. Extract magic constants: `long userId = 1L`. 3. it is unclear why `Mockito.when(beanMock.getUserId()).thenReturn(2l); ` returrns 2L, not 1L (again, magic constant) – Lesiak May 28 '20 at 07:06
0

For me the issue was occurring due to stubbing the behaviour of another mock inside before 'thenReturn' instruction is completed

Old code was:

Mockito.doReturn(Unit).whenever(mockObj)
            .login(userObject, spyMainActivityViewModel.onLoginListener)

I changed it to

val listener = spyMainActivityViewModel.onLoginListener
Mockito.doReturn(Unit).whenever(mockObj)
            .login(userObject, listener)

(https://github.com/mdhpuri355/Mockito/blob/master/src/main/java/org/mockito/internal/exceptions/Reporter.java. Refer unfinishedStubbing() Hint 3)

Astha Garg
  • 1,022
  • 7
  • 21