Servlet单元测试问题:Context Listener初始化DAO致测试失败
解决Servlet单元测试中Context Listener初始化DAO导致的测试失败问题
我来帮你搞定这个头疼的单元测试问题!首先咱们得搞清楚根源:
在Tomcat这类Servlet容器里,你的Context Listener会在应用启动时自动执行,把UserDaoImpl实例存入ServletContext,所以AllUsersServlet的init()方法能顺利拿到DAO实例。但单元测试是脱离容器运行的,没有容器帮你触发Listener的初始化逻辑,导致userDao一直是null,自然测试就挂了。
下面给你两种可行的解决方案,按需选择:
方案1:给Servlet添加依赖注入入口(推荐,低耦合易测试)
这种方式不影响生产环境使用,还能让测试变得简单,核心是给Servlet加一个setter方法来注入DAO,摆脱对ServletContext的强依赖。
第一步:修改你的AllUsersServlet
新增一个setter方法,保留原有init逻辑兼容生产环境:
public class AllUsersServlet extends HttpServlet { private static final String LIST_OF_USERS = "/WEB-INF/view/usersList.jsp"; private static final String DAO = "userDao"; private static final String USERS = "users"; private UserDaoImpl userDao; // 保留原有init逻辑,适配生产环境 @Override public void init() throws ServletException { userDao = (UserDaoImpl) getServletContext().getAttribute(DAO); } // 新增setter,专门用于测试时注入DAO public void setUserDao(UserDaoImpl userDao) { this.userDao = userDao; } // 你的doGet/doPost业务逻辑... }
第二步:编写单元测试(以JUnit+Mockito为例)
直接注入mock或真实DAO实例,完全不需要依赖容器:
import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.RequestDispatcher; import java.util.Arrays; import java.util.List; import static org.mockito.Mockito.*; public class AllUsersServletTest { @Mock private HttpServletRequest mockRequest; @Mock private HttpServletResponse mockResponse; @Mock private RequestDispatcher mockDispatcher; @Mock private UserDaoImpl mockUserDao; public AllUsersServletTest() { MockitoAnnotations.openMocks(this); } @Test public void testDoGet_ShouldForwardToUsersListWithUsers() throws Exception { // 1. 准备测试数据 List<User> testUsers = Arrays.asList(new User("Alice"), new User("Bob")); when(mockUserDao.getAllUsers()).thenReturn(testUsers); // 2. 模拟请求转发逻辑 when(mockRequest.getRequestDispatcher(LIST_OF_USERS)).thenReturn(mockDispatcher); // 3. 创建Servlet实例并注入mock DAO AllUsersServlet servlet = new AllUsersServlet(); servlet.setUserDao(mockUserDao); // 4. 执行测试方法 servlet.doGet(mockRequest, mockResponse); // 5. 验证业务逻辑是否正确 verify(mockRequest).setAttribute(USERS, testUsers); verify(mockDispatcher).forward(mockRequest, mockResponse); } }
方案2:在测试中模拟ServletContext(不修改原有代码)
如果你不想改动现有Servlet代码,可以用Mockito模拟ServletContext,让init()方法能拿到DAO实例:
@Test public void testDoGet_WithMockServletContext() throws Exception { // 1. 准备mock DAO和测试数据 UserDaoImpl mockUserDao = mock(UserDaoImpl.class); List<User> testUsers = Arrays.asList(new User("Charlie")); when(mockUserDao.getAllUsers()).thenReturn(testUsers); // 2. 模拟ServletContext,让init()能获取到DAO ServletContext mockContext = mock(ServletContext.class); when(mockContext.getAttribute(DAO)).thenReturn(mockUserDao); // 3. 创建Servlet并初始化(传入mock的ServletConfig) AllUsersServlet servlet = new AllUsersServlet(); servlet.init(new MockServletConfig(mockContext)); // MockServletConfig可自行实现或使用第三方库 // 4. 模拟请求和响应,执行测试 HttpServletRequest mockRequest = mock(HttpServletRequest.class); HttpServletResponse mockResponse = mock(HttpServletResponse.class); RequestDispatcher mockDispatcher = mock(RequestDispatcher.class); when(mockRequest.getRequestDispatcher(LIST_OF_USERS)).thenReturn(mockDispatcher); servlet.doGet(mockRequest, mockResponse); // 验证逻辑正确性 verify(mockRequest).setAttribute(USERS, testUsers); verify(mockDispatcher).forward(mockRequest, mockResponse); }
小提示
单元测试的核心是验证Servlet的业务逻辑(比如是否正确获取用户列表、是否转发到正确页面),而非容器初始化流程。方案1的依赖注入方式更符合测试原则,还能降低代码耦合度,后续维护更省心。
内容的提问来源于stack exchange,提问作者Sam Kilanoff
相关产品推荐
相关产品推荐

