EntityFrameworkCore单元测试:无法注入Mock DbContext至DAO
EF Core Database First模式下Mock DbContext单元测试报错解决方案
问题根源
Moq创建类代理对象时,默认会尝试调用无参构造函数,但你的EfDbContext仅提供了两个带参数的构造函数,没有公开无参构造函数,因此抛出「找不到无参构造函数」的异常。
解决方法
方法1:给Moq提供构造函数参数
直接使用EfDbContext中带DbContextOptions<EfDbContext>的构造函数,创建Mock时传入该参数的实例(可以是空配置):
// 构造DbContextOptions的Mock实例 var mockDbOptions = new Mock<DbContextOptions<EfDbContext>>(); // 传入参数创建EfDbContext的Mock var mockContext = new Mock<EfDbContext>(mockDbOptions.Object);
方法2:面向接口依赖(推荐)
让DAO依赖DbContext的接口而非具体类,从根源降低耦合,Mock更灵活:
- 定义DbContext接口:
public interface IEfDbContext { DbSet<SampleEntity> SampleEntities { get; set; } Task<int> SaveChangesAsync(CancellationToken cancellationToken = default); }
- 让
EfDbContext实现该接口:
public class EfDbContext : DbContext, IEfDbContext { // 保留原有构造函数与OnConfiguring逻辑 private readonly ISqlConnectionProvider _provider; public EfDbContext(ISqlConnectionProvider provider) { _provider = provider; } public EfDbContext(DbContextOptions<EfDbContext> options) : base(options) {} protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { if (!optionsBuilder.IsConfigured) { optionsBuilder.UseSqlServer(_provider.ConnectionString); } } // 实现接口成员 public DbSet<SampleEntity> SampleEntities { get; set; } public DbSet<ArbReservationEntity> ArbReservationEntities { get; set; } public Task<int> SaveChangesAsync(CancellationToken cancellationToken = default) { return base.SaveChangesAsync(cancellationToken); } }
- 修改
SampleDAO依赖接口:
public class SampleDAO : ISampleDao { private readonly ILogger _logHandler; private readonly IEfDbContext _dbContext; // 构造函数改为注入IEfDbContext public SampleDAO(ISqlConnectionProvider provider, ILogger logHandler, IEfDbContext dbContext) { _logHandler = logHandler; _dbContext = dbContext; // 移除硬编码的_sqlConnectionProvider初始化,改用构造函数传入的provider } // 原有AddAsync、GetAsync逻辑保持不变 public async Task AddAsync(SampleEntity entity) { _dbContext.SampleEntities.Add(entity); await _dbContext.SaveChangesAsync(); } public async Task<SampleEntity> GetAsync(long uniqueId) { return await _dbContext.SampleEntities.FirstOrDefaultAsync(a => a.Id == uniqueId); } }
- 测试时Mock接口:
var mockContext = new Mock<IEfDbContext>(); // 配置DbSet的查询逻辑 mockContext.Setup(c => c.SampleEntities).Returns(mockSet.Object);
测试代码额外优化点
- 原测试构造函数中
_dbContext为null,直接实例化SampleDAO会引发空引用,建议移除该初始化,在测试方法内按需创建DAO实例。 - 原
SampleDAO构造函数硬编码创建TestSqlConnectionProvider,违反依赖注入原则,应改为通过构造函数参数传入,测试时MockISqlConnectionProvider即可。
修正后的完整测试示例
using Xunit; using Moq; using NSubstitute; using Microsoft.EntityFrameworkCore; using System.Linq; public class SampleDAOTests { private readonly ILogger _logger; private readonly ISqlConnectionProvider _sqlConnectionProvider; public SampleDAOTests() { _logger = Substitute.For<ILogger>(); _sqlConnectionProvider = Substitute.For<ISqlConnectionProvider>(); } [Fact] public async Task GetAsync_WhenEntityExists_ReturnsMatchingEntity() { // 准备测试数据 var testEntities = new List<SampleEntity> { new() { Id = 1, CartId = "cart_1" }, new() { Id = 2, CartId = "cart_2" } }; // Mock DbSet并配置IQueryable行为(保证Linq查询正常工作) var mockSet = new Mock<DbSet<SampleEntity>>(); mockSet.As<IQueryable<SampleEntity>>() .Setup(m => m.Provider) .Returns(testEntities.AsQueryable().Provider); mockSet.As<IQueryable<SampleEntity>>() .Setup(m => m.Expression) .Returns(testEntities.AsQueryable().Expression); mockSet.As<IQueryable<SampleEntity>>() .Setup(m => m.ElementType) .Returns(testEntities.AsQueryable().ElementType); mockSet.As<IQueryable<SampleEntity>>() .Setup(m => m.GetEnumerator()) .Returns(testEntities.GetEnumerator()); // Mock DbContext(这里用方法1的方式,若用方法2则Mock IEfDbContext) var mockOptions = new Mock<DbContextOptions<EfDbContext>>(); var mockContext = new Mock<EfDbContext>(mockOptions.Object); mockContext.Setup(c => c.SampleEntities).Returns(mockSet.Object); // 初始化DAO var sampleDao = new SampleDAO(_sqlConnectionProvider, _logger, mockContext.Object); // 执行测试 var result = await sampleDao.GetAsync(1); // 断言 Assert.NotNull(result); Assert.Equal(1, result.Id); Assert.Equal("cart_1", result.CartId); } }
内容的提问来源于stack exchange,提问作者Krishnan
相关产品推荐
相关产品推荐

