You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 01:30:20