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

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更灵活:

  1. 定义DbContext接口:
public interface IEfDbContext
{
    DbSet<SampleEntity> SampleEntities { get; set; }
    Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
}
  1. 让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);
    }
}
  1. 修改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);
    }
}
  1. 测试时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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 01:30:59