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

重构C# Entity Framework 6项目以Dependancy Injection实现Moq模拟

重构代码以支持依赖注入和EF上下文Mocking

要解决这个问题,核心是移除方法内直接实例化EF上下文的硬编码,通过依赖注入(DI)将上下文注入到类中,这样单元测试时就能轻松用Moq替换真实的数据库上下文。下面分步骤来说明:

1. 重构现有类,引入构造函数注入

首先,你需要把EntitiesMigrate的实例化从方法内部移到类的构造函数里,让依赖从外部传入:

public class YourDataService
{
    private readonly EntitiesMigrate _dbContext;

    // 通过构造函数注入上下文
    public YourDataService(EntitiesMigrate dbContext)
    {
        _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
    }

    public int GetNumberOfLines(int groupId)
    {
        // 直接使用注入的上下文,不再new
        return _dbContext.MergeUserLines.ByGroup(groupId)
                         .GroupBy(x => x.Number)
                         .Count();
    }
}

如果你的EntitiesMigrate是从DbContext派生的,建议抽象出一个接口(比如IEntitiesMigrate),这样后续替换或扩展会更灵活:

public interface IEntitiesMigrate
{
    DbSet<MergeUserLine> MergeUserLines { get; set; }
    // 其他必要的DbSet或方法
}

public class EntitiesMigrate : DbContext, IEntitiesMigrate
{
    // 原有的上下文实现,包括AutoLocal()扩展方法的逻辑
}

// 然后修改服务类的构造函数
public YourDataService(IEntitiesMigrate dbContext)
{
    _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
}

关于AutoLocal()扩展方法:在生产环境中,你可以在DI容器注册时,预先创建好调用AutoLocal()后的EntitiesMigrate实例,再注入到服务类中。

2. 用Moq模拟EF上下文和DbSet

接下来,编写单元测试时,我们需要模拟MergeUserLines的数据集,让它返回我们预设的测试数据。这里以Moq为例:

首先,准备测试用的MergeUserLine数据:

var testLines = new List<MergeUserLine>
{
    new MergeUserLine { GroupId = 1, Number = "Line1" },
    new MergeUserLine { GroupId = 1, Number = "Line1" },
    new MergeUserLine { GroupId = 1, Number = "Line2" },
    new MergeUserLine { GroupId = 2, Number = "Line3" }
}.AsQueryable();

然后,模拟DbSet<MergeUserLine>:

var mockDbSet = new Mock<DbSet<MergeUserLine>>();
mockDbSet.As<IQueryable<MergeUserLine>>().Setup(m => m.Provider).Returns(testLines.Provider);
mockDbSet.As<IQueryable<MergeUserLine>>().Setup(m => m.Expression).Returns(testLines.Expression);
mockDbSet.As<IQueryable<MergeUserLine>>().Setup(m => m.ElementType).Returns(testLines.ElementType);
mockDbSet.As<IQueryable<MergeUserLine>>().Setup(m => m.GetEnumerator()).Returns(testLines.GetEnumerator());

如果你的ByGroup是自定义的扩展方法,需要确保它是基于IQueryable的,这样Moq能正确拦截。

接下来,模拟上下文:

var mockContext = new Mock<IEntitiesMigrate>(); // 或者Mock<EntitiesMigrate>如果没抽象接口
mockContext.Setup(c => c.MergeUserLines).Returns(mockDbSet.Object);

3. 编写单元测试

最后,创建服务类的实例,注入Mock的上下文,调用方法并验证结果:

[Test]
public void GetNumberOfLines_WithGroupId1_Returns2()
{
    // 准备数据和Mock(上面的代码)
    var service = new YourDataService(mockContext.Object);
    
    var result = service.GetNumberOfLines(1);
    
    Assert.AreEqual(2, result); // 因为GroupId=1的有2个不同的Number(Line1和Line2)
}

额外提示

  • 如果你的EF版本是EF Core,可以使用Microsoft.EntityFrameworkCore.InMemory内存数据库来替代Moq,有时候会更简单,尤其是处理复杂查询时。
  • 确保DI容器在生产环境中正确注册上下文:比如在ASP.NET Core中,你可以在Program.cs里添加:
    builder.Services.AddScoped<IEntitiesMigrate>(sp => new EntitiesMigrate().AutoLocal());
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 10:25:16