
本文讲解在 tdd 中如何正确 mock dao 层进行单元测试,避免因错误模拟 connection 或依赖未初始化对象导致 `saveticket()` 始终返回 `false` 的常见问题。核心在于:只 mock 待测类的直接依赖(如 dao),而非其内部实现细节(如 connection、preparedstatement)。
在基于 Mockito 的单元测试中,一个典型误区是试图“模拟整个数据库调用链”——比如同时 Mock Connection、DataBaseTestConfig 甚至 PreparedStatement。这不仅违背了测试隔离原则,还会因 mock 对象未正确 stub 行为(例如 getConnection() 返回 null)导致 NullPointerException 或逻辑短路(如 ps 为 null,最终 saveTicket() 必然返回 false)。
正确的做法是:聚焦被测行为,仅 Mock 直接协作对象(Collaborator)。本例中,ParkingDataBaseIT 的目标是验证业务逻辑是否能正确调用 DAO 并处理其返回结果,而非测试 DAO 本身与数据库的交互。因此,应直接 Mock TicketDAO,并显式设定其 saveTicket() 方法的行为:
@Test
public void testSavingTicket_shouldReturnTrue() throws SQLException, ClassNotFoundException {
// Given: 创建真实 Ticket 实例 + Mock DAO
Ticket ticket = new Ticket();
ticket.setVehicleRegNumber("ABCDEF");
ticket.setPrice(0.0);
ticket.setInTime(new Date());
ticket.setOutTime(null);
TicketDAO mockTicketDao = mock(TicketDAO.class);
when(mockTicketDao.saveTicket(ticket)).thenReturn(true); // 关键:预设返回值
// When: 调用被测方法
boolean saved = mockTicketDao.saveTicket(ticket);
// Then: 验证结果
assertTrue(saved);
assertThat(saved).isTrue();
}⚠️ 注意事项:
- 不要 Mock Connection 或 DataBaseTestConfig:它们属于 DAO 的内部实现细节,Mock 它们会使测试脆弱且难以维护;
- 确保方法签名匹配:若 saveTicket() 原方法需 Connection 参数,则 when(...) 中的 stub 必须包含相同参数(或使用 any(Connection.class));但更推荐重构 DAO 为无参 save(如 saveTicket(Ticket)),将连接管理交给 DAO 实现类,提升可测性;
- 避免静态 Mock 字段:原代码中 @Mock public static DataBaseTestConfig... 是无效的——@Mock 不支持静态字段,且 Mockito 无法注入静态变量,会导致 null 引用;
- 真实集成测试另作处理:若需验证 SQL 执行逻辑,应使用 H2 内存数据库 + @DataJpaTest 或真正的 MySQL 容器(如 Testcontainers),而非单元测试中强行模拟 JDBC 流程。
总结:单元测试的核心是验证行为契约,而非重演实现路径。通过精准 Mock 接口、明确设定输入输出,才能写出稳定、快速、高价值的测试用例。










