Search code examples
javaunit-testingservletsjunitmockito

Unit Testing Unit test Servlet with Mockito: Mocked Behavior Not Executing


I'm encountering an issue while writing a unit test for my servlet using Mockito. I'm attempting to mock the behavior of a Business Object (BO) object using doAnswer to set a test value to a Value Object (VO) object. However, it seems that the original BO object's behavior is still executing instead of the mocked behavior.Here's a simplified version of my servlet's doGet method:

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

MyVO myVO = new MyVO();

try (Connection connection = DBConnectionPooler.getDatabaseConnection("DBNAME")) {

    RequestDispatcher view;

    new MyBO().getAllItems(connection, myVO);

    request.setAttribute("myList", myVO.getMyList());

    view = request.getRequestDispatcher("/someJSP.jsp");

    view.forward(request, response);

} catch (Exception e) {

    logger.error(e);

}

}

And here's the relevant part of my test method using Mockito:

@Test
void testGetRequest() {

try {

    RequestDispatcher rd = mock(RequestDispatcher.class);

    HttpServletRequest request = mock(HttpServletRequest.class);

    HttpServletResponse response = mock(HttpServletResponse.class);

    MyBO myBO = mock(MyBO.class);



    MyVO myVO = new MyVO();

    List<TestObject> testList = new ArrayList<>();

    testList.add(new TestObject());

    testList.add(new TestObject());



    doAnswer(invocation -> {

        MyVO arg = invocation.getArgument(1);

        arg.setMyList(testList);

        return null;

    }).when(myBO).getAllItems(any(Connection.class), eq(myVO));
when(request.getRequestDispatcher("/someJSP.jsp")).thenReturn(rd);



    myServlet.doGet(request, response);



    verify(request).setAttribute("myList", testList);

    verify(rd).forward(request, response);

} catch (IOException | ServletException e) {

    e.printStackTrace();

    fail();
}

}

The issue arises when verifying request.setAttribute("myList", testList). The expected value is testList, but the actual value seems to be coming from the actual database instead of the mocked behavior.I'm using Mockito version 5.11.0 and JUnit version 5.10.2.Any insights or suggestions on what might be causing this discrepancy would be greatly appreciated. Thank you!


Solution

  • You cannot mock MyBo in the scenario. Reason is this line of code:

    new MyBO().getAllItems(connection, myVO);
    

    This will always create a real MyBO.

    What can you do to fix this?

    To use a mock you need to have some kind of injection point. E.g. by making it a field of your Servlet. Then you could inject the mock instead of a real MyBO and your servlet will call the mock object and execute the mock behaviour.

    To further explain this: A mock object is still only one instance of a class. It doesn't magically replace all instances with mocks. And neither can it alter the constructor of a class to create a mock. If you call a constructor of the class you will always get an instance of the real thing. This means inside your tests you need to inject your mock instance inside the class you are testing. The easiest way to do is via Constructor or setter. I am lacking more of your code to give you a working method for your problem but here is a simple example:

    public class Example {
    
        public String testMe() {
            return new MyBo().doSomething();
        }
    
    }
    

    now this again is not testable.

    We can change it like this:

    public class Example {
        
        private MyBo myBo;
    
        public Example(MyBo myBo) {
            this.myBo = myBo;
        }
    
        public String testMe() {
            return myBo.doSomething();
        }
    
    }
    

    (note: this slightly alters the behavior since we always using the Sam MyBo now, not new instances)

    and then you can test it like this

    @Test
    public void test() {
        MyBo mock = mock(MyBo.class);
    
        when(mock.doSomething()).thenReturn("mocked");
    
        Example underTest = new Example(mock); // this is the important change - Example now actually uses the mock
    
        assertThat(underTest.testMe()).isEqualTo("mocked");
    }