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

如何Mock QueryMultipleAsync及对使用该方法的代码做单元测试?

单元测试Dapper QueryMultipleAsync的可行方案

核心结论:必须封装为可Mock的接口

没错,要脱离真实数据库做单元测试,一定要把Dapper的数据库操作抽象成接口。直接在业务代码里写Dapper调用会让测试绑定数据库,没法单独验证业务逻辑。

最佳实践流程

  1. 抽象数据访问接口:把QueryMultipleAsync这类Dapper操作定义成接口方法,参数包含SQL语句、查询参数等必要信息。
  2. 实现数据访问层:在实现类里调用真实的Dapper方法,处理数据库连接。
  3. 业务层依赖接口:业务类通过构造函数注入这个接口,而非直接实例化数据访问实现类。
  4. Mock接口做单元测试:用Moq这类框架模拟接口的返回值,模拟多结果集的返回逻辑。

代码示例

1. 定义数据访问接口

public interface IDbDataAccessor
{
    Task<SqlMapper.GridReader> ExecuteQueryMultipleAsync(
        string sql, 
        object parameters = null, 
        IDbTransaction transaction = null, 
        CancellationToken cancellationToken = default);
}

2. 实现数据访问层

public class DbDataAccessor : IDbDataAccessor
{
    private readonly IDbConnection _connection;

    public DbDataAccessor(IDbConnection connection)
    {
        _connection = connection;
    }

    public async Task<SqlMapper.GridReader> ExecuteQueryMultipleAsync(
        string sql, 
        object parameters = null, 
        IDbTransaction transaction = null, 
        CancellationToken cancellationToken = default)
    {
        return await _connection.QueryMultipleAsync(
            sql, 
            param: parameters, 
            transaction: transaction, 
            cancellationToken: cancellationToken);
    }
}

3. 业务逻辑类(依赖接口)

public class DepartmentService
{
    private readonly IDbDataAccessor _dataAccessor;

    public DepartmentService(IDbDataAccessor dataAccessor)
    {
        _dataAccessor = dataAccessor;
    }

    public async Task<(List<Employee> Employees, List<Department> Departments)> GetDeptAndEmployeesAsync(int deptId)
    {
        var sql = @"
            SELECT * FROM Employees WHERE DepartmentId = @DeptId;
            SELECT * FROM Departments WHERE Id = @DeptId;";

        using var grid = await _dataAccessor.ExecuteQueryMultipleAsync(sql, new { DeptId = deptId });
        
        var employees = await grid.ReadAsync<Employee>().ToListAsync();
        var departments = await grid.ReadAsync<Department>().ToListAsync();
        
        return (employees, departments);
    }
}

// 实体类
public class Employee { public int Id { get; set; } public string Name { get; set; } public int DepartmentId { get; set; } }
public class Department { public int Id { get; set; } public string Name { get; set; } }

4. 单元测试(用Moq模拟)

[TestClass]
public class DepartmentServiceTests
{
    [TestMethod]
    public async Task GetDeptAndEmployeesAsync_ReturnsExpectedData()
    {
        // 准备测试数据
        var testEmployees = new List<Employee> { new Employee { Id = 1, Name = "Bob", DepartmentId = 2 } };
        var testDept = new List<Department> { new Department { Id = 2, Name = "Engineering" } };

        // Mock GridReader
        var mockGrid = new Mock<SqlMapper.GridReader>();
        mockGrid.Setup(g => g.ReadAsync<Employee>(It.IsAny<bool>()))
                .ReturnsAsync(testEmployees.AsAsyncEnumerable());
        mockGrid.Setup(g => g.ReadAsync<Department>(It.IsAny<bool>()))
                .ReturnsAsync(testDept.AsAsyncEnumerable());

        // Mock数据访问接口
        var mockAccessor = new Mock<IDbDataAccessor>();
        mockAccessor.Setup(a => a.ExecuteQueryMultipleAsync(
                It.IsAny<string>(), 
                It.Is<object>(p => ((dynamic)p).DeptId == 2), 
                null, 
                default))
            .ReturnsAsync(mockGrid.Object);

        // 初始化业务服务
        var service = new DepartmentService(mockAccessor.Object);
        
        // 执行测试方法
        var result = await service.GetDeptAndEmployeesAsync(2);
        
        // 断言结果
        Assert.AreEqual(1, result.Employees.Count);
        Assert.AreEqual("Bob", result.Employees[0].Name);
        Assert.AreEqual(1, result.Departments.Count);
        Assert.AreEqual("Engineering", result.Departments[0].Name);

        // 验证接口调用次数和参数
        mockAccessor.Verify(a => a.ExecuteQueryMultipleAsync(
                It.Is<string>(s => s.Contains("DepartmentId = @DeptId")), 
                It.Is<object>(p => ((dynamic)p).DeptId == 2), 
                null, 
                default), Times.Once);
    }
}

实用提示

  • 尽量避免直接MockSqlMapper.GridReader,更优雅的方式是在数据访问接口中直接返回封装好的结果(比如Task<(List<Employee>, List<Department>)>),这样Mock起来更简单,业务层也不用处理GridReader的细节。
  • 单元测试只关注业务逻辑的正确性:比如参数传递是否正确、结果是否按预期处理。Dapper本身的SQL执行正确性,应该用集成测试(连接真实数据库)来验证。
  • 如果业务逻辑依赖多结果集的顺序,Mock时一定要保证返回的顺序和真实场景完全一致,避免测试结果失真。

内容的提问来源于stack exchange,提问作者Antesh Pandey

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 10:43:18