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

XUnit测试中模拟IDataReader返回多条记录时遇NullReferenceException的解决方法

XUnit测试中模拟IDataReader返回多条记录时遇NullReferenceException的解决方法

我仔细看了你的测试代码和业务逻辑,发现NullReferenceException的根源很明确:你的Repository代码里用了IDataReader的字符串索引器(比如userReader["ID"]),但你完全没给这个索引器做Moq模拟。Moq的Mock<IDataReader>默认不会实现索引器的逻辑,所以代码读取userReader["ID"]时返回的是null,后续强制转换(int)null直接触发了空引用异常。

下面是一步步的修复方案,结合你的代码做具体调整:


1. 核心修复:模拟IDataReader的索引器行为

你需要在SetupAndGetMockDataReader方法中,添加对字符串索引器和整数索引器的模拟,让它们能根据当前行动态返回对应字段值。同时因为要逐行切换,我们可以直接在Read()方法的Setup里管理当前行的索引,不再需要原来的SetupNextRow方法。

修改后的SetupAndGetMockDataReader代码如下:

private Mock<IDataReader> SetupAndGetMockDataReader(List<EShopUser> eShopUsers)
{
    var mockreader = new Mock<IDataReader>();
    DataTable table = new DataTable();
    table.Columns.Add(new DataColumn("ID", typeof(Int32)));
    table.Columns.Add(new DataColumn("UserName", typeof(string)));
    table.Columns.Add(new DataColumn("FirstName", typeof(string)));
    table.Columns.Add(new DataColumn("LastName", typeof(string)));

    foreach (EShopUser item in eShopUsers)
    {
        DataRow row = table.NewRow();
        row["ID"] = item.ID;
        row["UserName"] = item.UserName;
        row["FirstName"] = item.FirstName;
        row["LastName"] = item.LastName;
        table.Rows.Add(row);
    }

    // 跟踪当前读取的行索引
    int currentRowIndex = -1;

    // 模拟Read()方法:移动到下一行,返回是否还有数据
    mockreader.Setup(r => r.Read())
        .Returns(() => 
        {
            currentRowIndex++;
            return currentRowIndex < table.Rows.Count;
        });

    // 模拟字符串索引器:userReader["ColumnName"]
    mockreader.Setup(r => r[It.IsAny<string>()])
        .Returns((string columnName) => 
        {
            if (currentRowIndex < 0 || currentRowIndex >= table.Rows.Count)
                throw new InvalidOperationException("当前没有可读取的行");
            
            int ordinal = table.Columns[columnName].Ordinal;
            return table.Rows[currentRowIndex][ordinal];
        });

    // 模拟整数索引器:userReader[ordinal](可选,防止后续代码用到)
    mockreader.Setup(r => r[It.IsAny<int>()])
        .Returns((int ordinal) => 
        {
            if (currentRowIndex < 0 || currentRowIndex >= table.Rows.Count)
                throw new InvalidOperationException("当前没有可读取的行");
            
            return table.Rows[currentRowIndex][ordinal];
        });

    // 保留原有的GetOrdinal设置(如果后续业务代码用到的话)
    for (int i = 0; i < table.Columns.Count; i++)
    {
        var name = table.Columns[i].ColumnName;
        mockreader.Setup(r => r.GetOrdinal(It.Is<string>(n => n == name))).Returns(i);
    }

    return mockreader;
}

2. 清理冗余代码

现在我们通过索引器动态返回字段值,原来的SetupNextRow方法已经没用了,可以直接删除它。

3. 确保IDbCommand的属性可被赋值

你的Repository代码里会设置cmd.CommandType和cmd.CommandText,需要给Moq的Command对象添加属性允许设置的配置,否则会触发未Setup的异常:

Mock<IDbCommand> command = new Mock<IDbCommand>();
// 允许设置CommandType和CommandText属性
command.SetupProperty(c => c.CommandType);
command.SetupProperty(c => c.CommandText);

4. 可选优化:修复Repository的伪异步代码

你的GetAllUsersAsync用Task.Run包裹同步数据库操作,属于“伪异步”写法,会浪费线程资源。可以简化为真正的同步转异步(如果用原生IDbConnection的话):

public Task<object> GetAllUsersAsync()
{
    List<EShopUser> users = new List<EShopUser>();
    using (_connection)
    {
        _connection.Open();
        IDbCommand cmd = _connection.CreateCommand();
        cmd.CommandType = System.Data.CommandType.StoredProcedure;
        cmd.CommandText = "GetAllUser";
        IDataReader userReader = cmd.ExecuteReader();
        while (userReader.Read())
        {
            users.Add(new EShopUser(
                (int)userReader["ID"], 
                (string)userReader["UserName"], 
                (string)userReader["FirstName"],
                (string)userReader["LastName"]));
        }
    }
    return Task.FromResult<object>(users);
}

现在运行你的XUnit测试,应该能正常通过了:userReader["ID"]等索引器会正确返回当前行的字段值,不再返回null,也就不会触发空引用异常。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 07:34:30