.NET MSTest使用Moq模拟DbContext报错的问题修复问询
问题原因
你遇到的Non-overridable members异常,是因为Moq只能模拟可重写的成员(虚方法/属性、接口成员),而你的TaskPlannerDbContext里的Tasks属性是自动生成的非虚属性,Moq没法拦截它的调用。
修复方案
下面给你三种可行的修复方式,按需选择:
方案一:给DbSet属性添加virtual修饰符
直接修改TaskPlannerDbContext中的Tasks属性,加上virtual关键字,让它变成可重写的:
public class TaskPlannerDbContext : DbContext { // 加上virtual修饰符 public virtual DbSet<Task> Tasks { get; set; } // 其他DbSet和配置... }
然后修正测试代码里的大小写错误(把c.tasks改成c.Tasks),同时补上loggerMock的初始化:
// 先初始化loggerMock var loggerMock = new Mock<ILogger<TaskService>>(); // 修正大小写:c.Tasks而不是c.tasks dbContextMock.Setup(c => c.Tasks).Returns(tasksDbSetMock.Object);
另外注意测试代码里的小错误:List<Task>里应该实例化Task而不是Expense:
var data = new List<Task> { new Task { Id = 1, Name = "BBB"}, new Task { Id = 2, Name = "ZZZ"}, new Task { Id = 3, Name = "AAA"}, }.AsQueryable();
方案二:抽象DbContext接口(推荐,符合依赖倒置)
定义一个ITaskPlannerDbContext接口,包含Tasks属性:
public interface ITaskPlannerDbContext { DbSet<Task> Tasks { get; set; } // 如果需要其他DbSet或方法,也可以加在这里 }
让TaskPlannerDbContext实现这个接口:
public class TaskPlannerDbContext : DbContext, ITaskPlannerDbContext { public DbSet<Task> Tasks { get; set; } // 其他代码... }
然后修改TaskService的构造函数,依赖接口而非具体的DbContext:
public class TaskService: ITask { private readonly ITaskPlannerDbContext _dbContext; private readonly ILogger<TaskService> _logger; public TaskService(ITaskPlannerDbContext dbContext, ILogger<TaskService> logger) { _dbContext = dbContext; _logger = logger; } // 其他方法不变... }
测试时直接mockITaskPlannerDbContext,就不会有非虚成员的问题了:
var dbContextMock = new Mock<ITaskPlannerDbContext>(); dbContextMock.Setup(c => c.Tasks).Returns(tasksDbSetMock.Object);
方案三:使用EF Core内存数据库(更贴近真实场景)
不需要mock DbSet,直接用EF Core的InMemory数据库来模拟真实的数据库操作,测试更可靠:
// 配置内存数据库 var options = new DbContextOptionsBuilder<TaskPlannerDbContext>() .UseInMemoryDatabase(databaseName: "TestTaskDb") .Options; // 创建DbContext并填充测试数据 using var context = new TaskPlannerDbContext(options); context.Tasks.AddRange( new Task { Id = 1, Name = "BBB"}, new Task { Id = 2, Name = "ZZZ"}, new Task { Id = 3, Name = "AAA"} ); context.SaveChanges(); // 初始化loggerMock和service var loggerMock = new Mock<ILogger<TaskService>>(); var service = new TaskService(context, loggerMock.Object); // 执行测试 var tasks2 = service.GetAllTasks(); Assert.AreEqual(3, tasks2.Count());
这种方式不需要处理mock的各种细节,测试逻辑更接近实际运行情况。
额外提示
你的测试代码里还有两个小问题:
List<Task>里错误实例化了Expense对象,要改成TaskloggerMock没有初始化,需要先创建var loggerMock = new Mock<ILogger<TaskService>>();
内容的提问来源于stack exchange,提问作者michal98
相关产品推荐
相关产品推荐

