使用Mockito测试Servlet时BO模拟行为未执行的问题排查
问题原因分析
- 核心问题:Servlet直接实例化MyBO,Mock对象未生效
Servlet的doGet方法里写的是new MyBO().getAllItems(...),每次调用都会生成全新的MyBO实例,和测试中Mock的myBO完全是两个独立对象。Mockito的Mock仅对创建的那个Mock实例有效,自然无法拦截真实BO的数据库查询逻辑。 - 参数匹配失败
测试中用eq(myVO)匹配VO参数,但Servlet里是自己new的MyVO,和测试中的myVO不是同一个对象——就算Mock对象能被用上,这个参数匹配也会失败,导致Mock逻辑不会触发。
解决建议
方案1:重构Servlet做依赖注入(推荐)
把MyBO改成Servlet的依赖,而非在方法内直接new,这样测试时就能替换成Mock对象。
修改Servlet代码:
public class MyServlet extends HttpServlet { private MyBO myBO; // 无参构造,兼容容器初始化 public MyServlet() { this.myBO = new MyBO(); } // 测试用构造函数,注入Mock的MyBO public MyServlet(MyBO myBO) { this.myBO = myBO; } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { MyVO myVO = new MyVO(); try (Connection connection = DBConnectionPooler.getDatabaseConnection("DBNAME")) { RequestDispatcher view; // 使用注入的myBO实例 myBO.getAllItems(connection, myVO); request.setAttribute("myList", myVO.getMyList()); view = request.getRequestDispatcher("/someJSP.jsp"); view.forward(request, response); } catch (Exception e) { logger.error(e); } } }
修改测试代码:
@Test void testGetRequest() { try { RequestDispatcher rd = mock(RequestDispatcher.class); HttpServletRequest request = mock(HttpServletRequest.class); HttpServletResponse response = mock(HttpServletResponse.class); MyBO myBO = mock(MyBO.class); // 用带参构造初始化Servlet,传入Mock的MyBO MyServlet myServlet = new MyServlet(myBO); List<TestObject> testList = new ArrayList<>(); testList.add(new TestObject()); testList.add(new TestObject()); // 改用any(MyVO.class)匹配,因为Servlet会自己new VO doAnswer(invocation -> { MyVO arg = invocation.getArgument(1); arg.setMyList(testList); return null; }).when(myBO).getAllItems(any(Connection.class), any(MyVO.class)); 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(); } }
方案2:用@InjectMocks注入Mock(适合小改场景)
如果没法修改构造函数,把MyBO改成Servlet的成员变量,再用Mockito的@InjectMocks自动注入Mock对象。
修改Servlet:
public class MyServlet extends HttpServlet { private MyBO myBO = new MyBO(); // 成员变量,而非局部new @Override protected void doGet(...) { // ... myBO.getAllItems(connection, myVO); // 使用成员变量 // ... } }
测试代码:
@ExtendWith(MockitoExtension.class) class MyServletTest { @Mock private MyBO myBO; @Mock private HttpServletRequest request; @Mock private HttpServletResponse response; @Mock private RequestDispatcher rd; @InjectMocks private MyServlet myServlet; @Test void testGetRequest() throws IOException, ServletException { 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), any(MyVO.class)); when(request.getRequestDispatcher("/someJSP.jsp")).thenReturn(rd); myServlet.doGet(request, response); verify(request).setAttribute("myList", testList); verify(rd).forward(request, response); } }
额外优化:Mock数据库连接
当前测试会调用真实的数据库连接池方法,建议用Mockito的mockStatic拦截静态方法,避免依赖外部数据库:
try (MockedStatic<DBConnectionPooler> mockedPooler = mockStatic(DBConnectionPooler.class)) { // Mock数据库连接,避免真实调用 mockedPooler.when(() -> DBConnectionPooler.getDatabaseConnection("DBNAME")) .thenReturn(mock(Connection.class)); myServlet.doGet(request, response); // 后续验证逻辑 }
内容的提问来源于stack exchange,提问作者mohd sabahat
相关产品推荐
相关产品推荐

