对注入DbContext参数的控制器进行单元测试报错如何解决?
问题根源
你遇到的报错和真实数据库连接没有关系,是Mock配置不完整导致的,具体原因如下:
- 直接Mock
ToDoContext时,没有为TodoItem属性(即DbSet<TodoItem>类型)设置Mock返回值,执行到_context.TodoItem.FindAsync()逻辑时会先抛出空引用异常 FindAsync是DbSet<T>的方法,你原本的写法直接在DbContext层面设置FindAsync的Mock规则,根本不会生效
解决方案
提供两种可直接运行的实现方案:
方案1:使用EF Core内存数据库(推荐)
这是微软官方推荐的EF Core业务逻辑单元测试方案,不需要复杂Mock配置,更贴近真实运行场景,首先需要在测试项目安装Nuget包Microsoft.EntityFrameworkCore.InMemory,测试代码如下:
[Fact] public async Task GetTodoItem_WithUnexistingItem_ReturnsNotFound() { // Arrange 为每个测试用例生成独立的内存数据库,避免数据互相干扰 var options = new DbContextOptionsBuilder<ToDoContext>() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; using var context = new ToDoContext(options); var controller = new TodoItemsController(context); // Act var result = await controller.GetTodoItem(long.MinValue); // Assert Assert.IsType<NotFoundResult>(result.Result); }
方案2:使用Moq完整Mock依赖
如果要坚持用纯Mock的方式实现,需要先MockDbSet<TodoItem>再绑定到Mock的DbContext上,测试代码如下:
[Fact] public async Task GetTodoItem_WithUnexistingItem_ReturnsNotFound() { // Arrange 先Mock DbSet<TodoItem>,配置FindAsync的返回规则 var mockDbSet = new Mock<DbSet<TodoItem>>(); mockDbSet.Setup(ds => ds.FindAsync(It.IsAny<long>())) .ReturnsAsync((TodoItem)null); // 再Mock ToDoContext,绑定上面生成的Mock DbSet var dbContextStub = new Mock<ToDoContext>(new DbContextOptions<ToDoContext>()); dbContextStub.Setup(context => context.TodoItem) .Returns(mockDbSet.Object); var controller = new TodoItemsController(dbContextStub.Object); // Act var result = await controller.GetTodoItem(long.MinValue); // Assert Assert.IsType<NotFoundResult>(result.Result); }
如果后续需要测试查询存在数据的场景,纯Mock方案还需要额外配置DbSet的IQueryable相关实现,内存数据库方案不需要额外修改,会更省心。
内容的提问来源于stack exchange,提问作者MrEmper
相关产品推荐
相关产品推荐

