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

如何用C#结合Moq在不影响真实数据库时单元测试SQL查询

单元测试中执行SQL脚本但不影响真实数据库的几种方案

方案一:重构代码+Mock IDbConnection(推荐,纯单元测试)

原代码直接在方法内实例化SqlConnection,导致无法替换为测试用的模拟对象。先重构代码,把数据库连接的创建逻辑抽离为抽象依赖:

重构后的业务代码

// 定义连接工厂接口
public interface IDbConnectionFactory
{
    IDbConnection CreateConnection(string connectionString);
}

// 生产环境用的SQL连接工厂实现
public class SqlConnectionFactory : IDbConnectionFactory
{
    public IDbConnection CreateConnection(string connectionString)
    {
        return new SqlConnection(connectionString);
    }
}

// 修改业务类,通过构造函数注入工厂
public class ContentService
{
    private readonly IDbConnectionFactory _dbConnectionFactory;
    private readonly Content _content;

    public ContentService(IDbConnectionFactory dbConnectionFactory, Content content)
    {
        _dbConnectionFactory = dbConnectionFactory;
        _content = content;
    }

    public void DeleteContentData(string connectionString)
    {
        using IDbConnection db = _dbConnectionFactory.CreateConnection(connectionString);
        DynamicParameters dynParameters1 = new DynamicParameters();
        dynParameters1.Add("@contentId", _content.content_id);
        db.Execute("DELETE FROM [content] WHERE content_id=@contentId", dynParameters1);
        db.Execute("INSERT INTO [content] (content_id) VALUES (@contentId);", dynParameters1);
    }
}

单元测试代码(用Moq)

现在可以Mock工厂和连接对象,验证SQL语句、参数是否被正确调用,完全不需要操作真实数据库:

[Test]
public void DeleteContentData_ShouldExecuteExpectedSql()
{
    // 准备测试数据
    var testContentId = 123;
    var mockContent = new Content { content_id = testContentId };

    // Mock连接工厂和连接对象
    var mockConnectionFactory = new Mock<IDbConnectionFactory>();
    var mockConnection = new Mock<IDbConnection>();
    mockConnectionFactory.Setup(f => f.CreateConnection(It.IsAny<string>())).Returns(mockConnection.Object);

    // 验证DELETE语句的调用参数
    mockConnection.Setup(c => c.Execute(
        "DELETE FROM [content] WHERE content_id=@contentId",
        It.Is<DynamicParameters>(p => p.Get<int>("@contentId") == testContentId),
        null, null, null))
        .Verifiable();

    // 验证INSERT语句的调用参数
    mockConnection.Setup(c => c.Execute(
        "INSERT INTO [content] (content_id) VALUES (@contentId);",
        It.Is<DynamicParameters>(p => p.Get<int>("@contentId") == testContentId),
        null, null, null))
        .Verifiable();

    var service = new ContentService(mockConnectionFactory.Object, mockContent);

    // 执行测试方法
    service.DeleteContentData("fake_connection_string");

    // 验证所有预期调用都已执行
    mockConnection.VerifyAll();
}

方案二:使用内存测试数据库(集成测试,真执行SQL)

如果需要验证SQL语法的正确性,可以用内存数据库,比如SQLite内存模式,数据在连接关闭后自动销毁:

[Test]
public void DeleteContentData_ShouldPerformCorrectOperations()
{
    // 创建SQLite内存数据库连接
    var connectionString = "Data Source=:memory:";
    using var connection = new SqliteConnection(connectionString);
    connection.Open();

    // 初始化和真实库一致的表结构
    connection.Execute("CREATE TABLE [content] (content_id INT PRIMARY KEY);");
    // 插入测试数据
    connection.Execute("INSERT INTO [content] (content_id) VALUES (123);");

    // 执行业务方法
    var service = new ContentService(new SqlConnectionFactory(), new Content { content_id = 123 });
    service.DeleteContentData(connectionString);

    // 验证操作结果:删除后插入,记录数不变
    var count = connection.QueryFirst<int>("SELECT COUNT(*) FROM [content] WHERE content_id=123");
    Assert.AreEqual(1, count);
}

如果需要更贴近SQL Server环境,也可以用LocalDB创建临时测试库,测试前建表、测试后删库。

方案三:事务回滚(适合依赖真实库结构的场景)

如果必须使用和生产一致的数据库结构,但不想留存测试数据,可以用事务包裹测试流程,结束后回滚:

[Test]
public void DeleteContentData_ShouldRollbackAfterTest()
{
    var connectionString = "your_test_db_connection_string";
    using var connection = new SqlConnection(connectionString);
    connection.Open();
    using var transaction = connection.BeginTransaction();

    try
    {
        // 这里需要调整业务方法,允许传入已打开的连接(而非自己创建)
        var service = new ContentService(new SqlConnectionFactory(), new Content { content_id = 456 });
        service.DeleteContentData(connection); // 修改方法参数为IDbConnection

        // 验证操作是否生效
        var count = connection.QueryFirst<int>("SELECT COUNT(*) FROM [content] WHERE content_id=456", transaction: transaction);
        Assert.AreEqual(1, count);

        // 手动回滚事务,所有操作不会持久化
        transaction.Rollback();
    }
    catch
    {
        transaction.Rollback();
        throw;
    }
}

内容的提问来源于stack exchange,提问作者Aman Tiwari

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 15:43:14