重构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
相关产品推荐
相关产品推荐

