C#单元测试中Mock SqlConnection未生效仍创建真实连接问题
问题原因
你当前的Mock代码完全不生效,核心有两个硬伤:
ExecuteGetObj方法内部直接通过new SqlConnection(...)硬编码创建真实数据库连接实例,和你在测试里构造的Mock<IDbConnection>对象没有任何关联,方法执行时根本不会获取你构造的Mock实例,自然会走真实的数据库连接逻辑。- 方法内的
SqlCommand也是直接通过new SqlCommand(query, connection)创建的,没有调用connection.CreateCommand()方法,你之前写的connection.Setup(m => m.CreateCommand())逻辑完全没有触发入口。
注意:Moq这类基于动态代理的Mock框架,只能Mock接口、虚方法/抽象方法,根本无法拦截非虚的具体类实例化、静态方法调用,直接硬编码在方法内部的new操作,靠常规Moq写法是不可能替换成Mock对象的。
推荐解决方案:重构代码实现依赖倒置
这是可维护性最高、符合单元测试最佳实践的方案,不需要依赖特殊的Mock框架,也可以兼容原有方法的静态属性。
- 抽象数据库连接创建逻辑,不要在业务方法内直接new连接对象
/// <summary> /// 连接工厂抽象,隔离连接创建逻辑 /// </summary> public interface IDbConnectionFactory { IDbConnection CreateConnection(); } /// <summary> /// 生产环境用的真实连接工厂实现 /// </summary> public class SqlConnectionFactory : IDbConnectionFactory { private readonly string _connStr; public SqlConnectionFactory(string connectionString) { _connStr = connectionString; } public IDbConnection CreateConnection() { return new SqlConnection(_connStr); } }
- 改造原
ExecuteGetObj方法,将依赖作为参数传入,同时调整Command创建逻辑,不要直接硬编码实例化SqlCommand
public static object ExecuteGetObj(string query, IDbConnectionFactory connectionFactory) { object obj = null; using (var connection = connectionFactory.CreateConnection()) { connection.Open(); // 改用连接对象的CreateCommand方法创建命令,方便Mock替换 using (IDbCommand cmd = connection.CreateCommand()) { cmd.CommandText = query; obj = cmd.ExecuteScalar(); } } return obj; }
- 调整单元测试代码,通过Mock工厂注入你构造的Mock连接、Mock命令
// 构造Mock命令 var command = new Mock<IDbCommand>(); command.Setup(x => x.ExecuteScalar()) .Returns(null) // 显式指定返回值匹配断言预期 .Verifiable(); // 构造Mock连接 var connection = new Mock<IDbConnection>(); connection.Setup(m => m.Open()); // 配置Open方法行为,默认不抛错即可 connection.Setup(m => m.CreateCommand()) .Returns(command.Object); // 构造Mock连接工厂 var mockFactory = new Mock<IDbConnectionFactory>(); mockFactory.Setup(f => f.CreateConnection()) .Returns(connection.Object); // 执行测试方法,注入Mock依赖 var obj = Common.ExecuteGetObj("select 1", mockFactory.Object); // 断言 Assert.IsNull(obj); command.Verify(x => x.ExecuteScalar(), Times.Once); // 验证方法确实按预期执行
生产环境调用该方法时,传入真实的SqlConnectionFactory实例即可,不会影响原有业务逻辑。
备选方案:使用支持实例化拦截的Mock框架
如果你暂时无法重构生产代码,可以使用支持拦截非虚方法、new实例化操作的Mock框架,比如JustMock、Microsoft Fakes、Pose等,直接在测试中拦截new SqlConnection、new SqlCommand的操作,返回你构造的Mock对象。但这类方案要么是商业付费框架,要么配置复杂、和运行环境绑定紧,长期可维护性远不如重构依赖的方案,不推荐优先使用。
内容的提问来源于stack exchange,提问作者Hary
相关产品推荐
相关产品推荐

