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

使用Moq模拟EF Core与Dapper上下文时构造函数异常的解决方案

解决Moq无法Mock DapperContext的异常问题

异常原因

Moq在Mock非抽象类时,默认要求目标类必须具备无参构造函数,而你的DapperContext仅提供了带IConfiguration参数的构造函数,导致Moq无法生成代理对象,从而抛出该异常。

解决方案

推荐两种可行方案,优先选择方案一(符合依赖倒置原则,更利于后续扩展):

方案一:为DapperContext抽象接口(最优解)

  1. 定义IDapperContext接口,包含核心方法:
public interface IDapperContext
{
    IDbConnection CreateConnection();
}
  1. 修改DapperContext实现该接口:
public class DapperContext : IDapperContext
{
    private readonly IConfiguration _configuration;
    private readonly string _connectionString;

    public DapperContext(IConfiguration configuration)
    {
        _configuration = configuration;
        _connectionString = _configuration["connectionStrings:DbContext"];
    }

    public IDbConnection CreateConnection()
    => new SqlConnection(_connectionString);
}
  1. 更新EmployeeRepository的依赖为接口类型:
public class EmployeeRepository : IEmployeeRepository
{
    private readonly DemoContext _context;
    private readonly IDapperContext _dapperContext; // 改为接口依赖

    public EmployeeRepository(DemoContext context, IDapperContext dapperContext)
    {
        _context = context;
        _dapperContext = dapperContext;
    }

    // GetAll方法逻辑保持不变
}
  1. 调整测试类,Mock接口而非具体类:
public class EmployeeRepositoryTest
{
    private readonly Mock<IDapperContext> _dapperContextMock;
    private readonly Mock<IConfiguration> _mockConfig;

    public EmployeeRepositoryTest()
    {
        _dapperContextMock = new Mock<IDapperContext>();
        _mockConfig = new Mock<IConfiguration>();
    }        

    [Fact]
    public async Task GetAllEmployeesShouldWorks() // xUnit推荐异步测试返回Task而非void
    {
        // arrange: 准备内存数据库和模拟数据
        var options = new DbContextOptionsBuilder<DemoContext>()
            .UseInMemoryDatabase(databaseName: "DemoApp")
            .Options;

        var employeeEntity = new Employee();
        employeeEntity.Name = "Test Employee";
        employeeEntity.Id = Guid.NewGuid();
        employeeEntity.DateAdmission = DateTime.Now;
        employeeEntity.CompanyId = Guid.NewGuid();

        using (var context = new DemoContext(options))
        {
            context.Add(employeeEntity);
            await context.SaveChangesAsync();
        }

        // Mock Dapper连接和查询结果
        var mockConnection = new Mock<IDbConnection>();
        var fakeEmployees = new List<EmployeeDTO> 
        { 
            new EmployeeDTO { Id = employeeEntity.Id, Name = employeeEntity.Name } 
        };
        mockConnection.Setup(c => c.QueryAsync<EmployeeDTO>(
            "usp_get_all_employees", 
            null, 
            null, 
            null, 
            CommandType.StoredProcedure))
                      .ReturnsAsync(fakeEmployees);

        _dapperContextMock.Setup(d => d.CreateConnection()).Returns(mockConnection.Object);

        // Act
        using (var context = new DemoContext(options))
        {
            var employeeRepository = new EmployeeRepository(context, _dapperContextMock.Object);
            var employees = await employeeRepository.GetAll();

            // Assert
            Assert.NotNull(employees);
            Assert.Single(employees);
            Assert.Equal("Test Employee", employees[0].Name);
        }
    }
}

方案二:创建DapperContext Mock时传入构造参数

若不想修改现有代码结构,可在创建DapperContext的Mock时,传入它依赖的IConfiguration参数:

public EmployeeRepositoryTest()
{
    _mockConfig = new Mock<IConfiguration>();
    // 配置Mock的IConfiguration返回模拟连接字符串
    _mockConfig.Setup(c => c["connectionStrings:DbContext"]).Returns("FakeConnectionString");
    // 传入构造参数生成DapperContext的Mock
    _dapperContextMock = new Mock<DapperContext>(_mockConfig.Object);
}

同时需要MockCreateConnection方法,避免调用真实数据库连接:

[Fact]
public async Task GetAllEmployeesShouldWorks()
{
    // ... 其他arrange代码

    var mockConnection = new Mock<IDbConnection>();
    var fakeEmployees = new List<EmployeeDTO> { new EmployeeDTO { Id = Guid.NewGuid(), Name = "Test Employee" } };
    mockConnection.Setup(c => c.QueryAsync<EmployeeDTO>(
        "usp_get_all_employees", 
        null, 
        null, 
        null, 
        CommandType.StoredProcedure))
                  .ReturnsAsync(fakeEmployees);

    _dapperContextMock.Setup(d => d.CreateConnection()).Returns(mockConnection.Object);

    using (var context = new DemoContext(options))
    {
        var employeeRepository = new EmployeeRepository(context, _dapperContextMock.Object);
        var employees = await employeeRepository.GetAll();

        Assert.NotNull(employees);
    }
}

额外提示

  • xUnit中异步测试方法优先返回Task,而非void,这样能正确捕获异步执行过程中的异常。
  • 测试Dapper查询逻辑时,必须MockIDbConnection的QueryAsync方法,避免依赖真实数据库环境。

内容的提问来源于stack exchange,提问作者Andres Guillen Hernandez

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 07:45:38