如何为调用存储过程与直接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
相关产品推荐
相关产品推荐

