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

如何为调用存储过程与直接SQL的DAL编写单元测试并Mock数据库结果

无返回值DAL方法的单元测试Mock方案

要Mock无返回值的数据库操作(比如调用存储过程执行ExecuteNonQuery),核心是先解耦代码对数据库具体实现的依赖,再用Mock框架验证方法的行为是否符合预期。以下是具体步骤和示例:

1. 先做代码解耦

你的现有代码直接依赖基类的GetConnection()和CreateSqlCommand()方法,无法直接Mock。需要先抽象这些数据库操作的接口,让业务类依赖接口而非具体实现:

定义数据库操作接口

public interface IDatabaseAccessor
{
    IDbConnection GetConnection();
    IDbCommand CreateSqlCommand(IDbConnection connection, string commandText);
}

重构Notification类

通过构造函数注入接口,替换原有的基类调用:

public class Notification : INotificationBal
{
    private readonly IDatabaseAccessor _dbAccessor;

    public Notification(IDatabaseAccessor dbAccessor)
    {
        _dbAccessor = dbAccessor;
    }

    public void InsertNotification(NotificationType notif)
    {
        using (var con = _dbAccessor.GetConnection())
        {
            using (var command = _dbAccessor.CreateSqlCommand(con, "sp_InsertNotifDetails"))
            {
                command.SetParamsValues(
                    ("@NotificationType", notif.NotificationType),
                    ("@NotificationName", notif.NotificationName));
                command.ExecuteNonQuery();
            }
        }
    }
}

2. 使用Moq编写单元测试

以Moq框架为例,我们需要Mock数据库连接、命令对象,重点验证存储过程名称是否正确、参数是否匹配、ExecuteNonQuery是否被调用:

[TestClass]
public class NotificationTests
{
    [TestMethod]
    public void InsertNotification_ExecutesStoredProcedureWithCorrectParams()
    {
        // 准备测试数据
        var testNotification = new NotificationType
        {
            NotificationType = 1,
            NotificationName = "Mobile"
        };

        // Mock IDbCommand:验证ExecuteNonQuery调用,捕获参数设置
        var mockCommand = new Mock<IDbCommand>();
        mockCommand.Setup(cmd => cmd.ExecuteNonQuery()).Verifiable();

        // Mock IDbConnection:返回Mock的Command对象
        var mockConnection = new Mock<IDbConnection>();
        mockConnection.Setup(conn => conn.CreateCommand()).Returns(mockCommand.Object);

        // Mock IDatabaseAccessor:返回Mock的连接和命令
        var mockDbAccessor = new Mock<IDatabaseAccessor>();
        mockDbAccessor.Setup(acc => acc.GetConnection()).Returns(mockConnection.Object);
        mockDbAccessor.Setup(acc => acc.CreateSqlCommand(mockConnection.Object, "sp_InsertNotifDetails"))
                      .Returns(mockCommand.Object);

        // 执行被测方法
        var notificationService = new Notification(mockDbAccessor.Object);
        notificationService.InsertNotification(testNotification);

        // 验证参数是否正确添加(假设SetParamsValues会调用Parameters.Add)
        mockCommand.Verify(cmd => cmd.Parameters.Add(It.Is<IDbDataParameter>(p =>
            p.ParameterName == "@NotificationType" && (int)p.Value == testNotification.NotificationType)), 
            Times.Once);
        mockCommand.Verify(cmd => cmd.Parameters.Add(It.Is<IDbDataParameter>(p =>
            p.ParameterName == "@NotificationName" && (string)p.Value == testNotification.NotificationName)), 
            Times.Once);

        // 验证ExecuteNonQuery被调用一次
        mockCommand.Verify(cmd => cmd.ExecuteNonQuery(), Times.Once);
    }
}

关键注意事项

  • 解耦是前提:必须把直接依赖的数据库操作抽象成接口,否则无法Mock。如果SetParamsValues是自定义扩展方法,建议把参数设置逻辑也封装到接口中,或者用Moq的Callback方法捕获参数。
  • 无返回值方法的测试重点:不需要验证数据库真实数据变化,而是验证方法的行为——比如是否调用了正确的存储过程、参数是否符合预期、核心操作(ExecuteNonQuery)是否执行。

内容的提问来源于stack exchange,提问作者Samuel David

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 14:10:16