Mock DAO保存对象返回false及PreparedStatement空指针问题排查
问题描述
我是测试驱动开发(TDD)的新手,现需验证对象是否成功保存至数据库。编写的测试代码中,Mock的TicketDAO调用saveTicket方法始终返回false,且在saveTicket方法内发现PreparedStatement(ps)为空指针,但Connection为mockConnection。实际使用MySQL时该保存请求可正常执行,仅测试阶段出现此问题。
测试代码
@ExtendWith(MockitoExtension.class) public class ParkingDataBaseIT { @Mock private TicketDAO mockTicketDAO; @Mock public static DataBaseTestConfig mockDataBaseTestConfig = new DataBaseTestConfig(); @Mock private static Connection mockConnection; @BeforeEach public void setup() throws SQLException, ClassNotFoundException { // Connection attempt. when(mockDataBaseTestConfig.getConnection()).thenReturn(mockConnection); } @AfterAll public static void tearDown() { // ... } @DisplayName("Tests if saving a ticket is successful.") @Test public void testParkingACarAlpha() throws SQLException, ClassNotFoundException { // Given. Date inTime = new Date(); Ticket ticket = new Ticket(); ticket.setVehicleRegNumber("ABCDEF"); ticket.setPrice(0); ticket.setInTime(inTime); ticket.setOutTime(null); // When. // MockDao always returning false when saving. final boolean saved = mockTicketDAO.saveTicket(ticket, mockDataBaseTestConfig.getConnection()); // Then. assertEquals(true, saved); assertThat(saved).isEqualTo(true); } }
建立MySQL连接方法
public Connection getConnection() throws ClassNotFoundException, SQLException { Class.forName("com.mysql.cj.jdbc.Driver"); return DriverManager.getConnection("jdbc:mysql://localhost:3306/testing", "root", ""); }
保存Ticket方法
public boolean saveTicket(Ticket ticket, Connection con) { try { PreparedStatement ps = con.prepareStatement("insert into ticket(vehicleRegNumber, price, inTime, outTime) values(?, ?, ?, ?, ?)"); ps.setInt(1, ticket.getParkingSpot().getId()); ps.setString(2, ticket.getVehicleRegNumber()); ps.setDouble(3, ticket.getPrice()); ps.setTimestamp(4, new Timestamp(ticket.getInTime().getTime())); ps.setTimestamp(5, (ticket.getOutTime() == null) ? null: (new Timestamp(ticket.getOutTime().getTime()))); return ps.execute(); } catch (Exception e) { logger.error("Cannot fetch available slot.", e); } finally { dataBaseConfig.closeConnection(con); } return false; }
问题原因分析
- 错误Mock了TicketDAO本身:用
@Mock创建的mockTicketDAO,Mockito对其方法调用默认返回类型的默认值(boolean类型为false),不会执行saveTicket的真实业务逻辑,这是测试返回false的直接原因。 - MockConnection未配置prepareStatement返回值:即使执行真实的
saveTicket逻辑,mock的Connection调用prepareStatement时会返回null,导致PreparedStatement ps空指针异常——因为未给mockConnection设置prepareStatement的返回结果。 - 测试用Ticket缺少parkingSpot属性:
saveTicket方法中调用了ticket.getParkingSpot().getId(),但测试代码里创建的Ticket对象未设置parkingSpot,即使走到真实逻辑也会触发另一个空指针异常。
解决方法
1. 使用真实TicketDAO或Spy替代Mock
如果要验证saveTicket的业务逻辑,不要直接Mock整个TicketDAO,应该使用真实实例,或用@Spy包装真实DAO(仅需部分mock时使用):
// 替换@Mock为真实实例或@InjectMocks private TicketDAO realTicketDAO = new TicketDAO(); // 或依赖注入场景下使用@InjectMocks @InjectMocks private TicketDAO realTicketDAO;
2. 配置MockConnection及相关对象的返回值
若坚持用Mock(避免连接真实数据库),需给mockConnection设置prepareStatement的返回值,同时Mock PreparedStatement并配置execute()的返回:
@Mock private PreparedStatement mockPs; @BeforeEach public void setup() throws SQLException, ClassNotFoundException { when(mockDataBaseTestConfig.getConnection()).thenReturn(mockConnection); // 配置mockConnection调用prepareStatement时返回mockPs when(mockConnection.prepareStatement(anyString())).thenReturn(mockPs); // 配置mockPs.execute()返回true when(mockPs.execute()).thenReturn(true); }
3. 完善测试用Ticket的属性
给测试中的Ticket设置parkingSpot,避免空指针:
// Given部分补充代码 ParkingSpot spot = new ParkingSpot(); spot.setId(1); // 设置测试用ID ticket.setParkingSpot(spot);
4. 调整测试验证逻辑
除验证返回值,还可验证DAO是否正确执行了SQL操作:
// Then部分补充验证逻辑 verify(mockConnection).prepareStatement(anyString()); verify(mockPs).setInt(1, 1); verify(mockPs).setString(2, "ABCDEF"); verify(mockPs).execute();
内容的提问来源于stack exchange,提问作者newenpoi
相关产品推荐
相关产品推荐

