使用Moq模拟EF Core与Dapper上下文时构造函数异常的解决方案
解决Moq无法Mock DapperContext的异常问题
异常原因
Moq在Mock非抽象类时,默认要求目标类必须具备无参构造函数,而你的DapperContext仅提供了带IConfiguration参数的构造函数,导致Moq无法生成代理对象,从而抛出该异常。
解决方案
推荐两种可行方案,优先选择方案一(符合依赖倒置原则,更利于后续扩展):
方案一:为DapperContext抽象接口(最优解)
- 定义
IDapperContext接口,包含核心方法:
public interface IDapperContext { IDbConnection CreateConnection(); }
- 修改
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); }
- 更新
EmployeeRepository的依赖为接口类型:
public class EmployeeRepository : IEmployeeRepository { private readonly DemoContext _context; private readonly IDapperContext _dapperContext; // 改为接口依赖 public EmployeeRepository(DemoContext context, IDapperContext dapperContext) { _context = context; _dapperContext = dapperContext; } // GetAll方法逻辑保持不变 }
- 调整测试类,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查询逻辑时,必须Mock
IDbConnection的QueryAsync方法,避免依赖真实数据库环境。
内容的提问来源于stack exchange,提问作者Andres Guillen Hernandez
相关产品推荐
相关产品推荐

