Moq FindAsync 方法 returns 空
Moq FindAsync method returns null
这几天我一直在尝试测试一种删除方法,但没有成功。
调试代码后,我意识到问题出在 FindAsync 方法 returns null 中,这导致测试陷入 NotFound()
条件.
由于我是 C#、.NET、EntityFramework 和 Moq 领域的新手,有人可以帮助我吗?
- 控制器
public async Task<IActionResult> DeleteTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
_context.TodoItems.Remove(todoItem);
await _context.SaveChangesAsync();
return NoContent();
}
- 测试
[Fact]
public async Task DeleteTodoItem_ShouldBeCallFindAsyncMethodOnce()
{
var todo = new TodoItem { Id = 1, Name = "test", IsComplete = true };
var mockSet = new Mock<DbSet<TodoItem>>();
var options = new DbContextOptionsBuilder<TodoContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
var mockContext = new Mock<TodoContext>(options);
mockContext.Setup(c => c.TodoItems).Returns(mockSet.Object);
mockContext.Setup(c => c.TodoItems.FindAsync(1)).ReturnsAsync(todo);
var service = new TodoItemsController(mockContext.Object);
var deleteTodo = await service.DeleteTodoItem(1);
mockSet.Verify(m => m.FindAsync(It.IsAny<TodoItem>()), Times.Once());
}
当您的控制器长时间传递它时,您正在为 int 上的 FindAsync 设置模拟并验证模拟。因此,您需要在 long 而不是 int 中设置模拟和验证。并且还在 DbSet 上设置 FindAsync 模拟,而不是在 DbContext 上。
例如:
var todo = new TodoItem { Id = 1, Name = "test", IsComplete = true };
var mockSet = new Mock<DbSet<TodoItem>>();
mockSet.Setup(s => s.FindAsync(1L)).ReturnsAsync(todo);
var mockContext = new Mock<TodoContext>();
mockContext.Setup(c => c.TodoItems).Returns(mockSet.Object);
var service = new TodoItemsController(mockContext.Object);
var deleteTodo = await service.DeleteTodoItem(1);
mockSet.Verify(m => m.FindAsync(1L), Times.Once());
完整的工作示例 here。下面用于验证的完整代码。
// Need package reference to Microsoft.EntityFrameworkCore v6.0.5
// Need package reference to Moq v4.18.1
using System;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Moq;
public class Program
{
public static async Task Main()
{
var todo = new TodoItem { Id = 1, Name = "test", IsComplete = true };
var mockSet = new Mock<DbSet<TodoItem>>();
mockSet.Setup(s => s.FindAsync(1L)).ReturnsAsync(todo);
var mockContext = new Mock<TodoContext>();
mockContext.Setup(c => c.TodoItems).Returns(mockSet.Object);
var service = new TodoItemsController(mockContext.Object);
var deleteTodo = await service.DeleteTodoItem(1);
mockSet.Verify(m => m.FindAsync(1L), Times.Once());
Console.WriteLine("Test complete without error");
}
}
public class TodoContext : DbContext
{
public virtual DbSet<TodoItem> TodoItems { get; set; }
}
public class TodoItem
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
}
public class TodoItemsController
{
readonly TodoContext _context;
public TodoItemsController(TodoContext context)
{
_context = context;
}
public async Task<IActionResult> DeleteTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
_context.TodoItems.Remove(todoItem);
await _context.SaveChangesAsync();
return NoContent();
}
public NotFoundResult NotFound() { return new NotFoundResult(); }
public NoContentResult NoContent() { return new NoContentResult(); }
}
public interface IActionResult{}
public class NotFoundResult : IActionResult {}
public class NoContentResult : IActionResult {}
请注意,此测试似乎不是特别有用。通常在单元测试时,我们会围绕结果进行断言,而不是围绕实现细节进行断言。无需断言 FindAsync 已被调用一次。它只会让测试更脆弱。如果您正在对操作方法进行单元测试,您需要确保在传入 non-existent 项目时获得 NotFoundResult,在传入现有项目时获得 NoContentResult,并且正确的项目从 DbSet 中删除。使用 in-memory DbContext 而不是模拟它可能会使事情变得更简单。
这几天我一直在尝试测试一种删除方法,但没有成功。
调试代码后,我意识到问题出在 FindAsync 方法 returns null 中,这导致测试陷入 NotFound()
条件.
由于我是 C#、.NET、EntityFramework 和 Moq 领域的新手,有人可以帮助我吗?
- 控制器
public async Task<IActionResult> DeleteTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
_context.TodoItems.Remove(todoItem);
await _context.SaveChangesAsync();
return NoContent();
}
- 测试
[Fact]
public async Task DeleteTodoItem_ShouldBeCallFindAsyncMethodOnce()
{
var todo = new TodoItem { Id = 1, Name = "test", IsComplete = true };
var mockSet = new Mock<DbSet<TodoItem>>();
var options = new DbContextOptionsBuilder<TodoContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
var mockContext = new Mock<TodoContext>(options);
mockContext.Setup(c => c.TodoItems).Returns(mockSet.Object);
mockContext.Setup(c => c.TodoItems.FindAsync(1)).ReturnsAsync(todo);
var service = new TodoItemsController(mockContext.Object);
var deleteTodo = await service.DeleteTodoItem(1);
mockSet.Verify(m => m.FindAsync(It.IsAny<TodoItem>()), Times.Once());
}
当您的控制器长时间传递它时,您正在为 int 上的 FindAsync 设置模拟并验证模拟。因此,您需要在 long 而不是 int 中设置模拟和验证。并且还在 DbSet 上设置 FindAsync 模拟,而不是在 DbContext 上。
例如:
var todo = new TodoItem { Id = 1, Name = "test", IsComplete = true };
var mockSet = new Mock<DbSet<TodoItem>>();
mockSet.Setup(s => s.FindAsync(1L)).ReturnsAsync(todo);
var mockContext = new Mock<TodoContext>();
mockContext.Setup(c => c.TodoItems).Returns(mockSet.Object);
var service = new TodoItemsController(mockContext.Object);
var deleteTodo = await service.DeleteTodoItem(1);
mockSet.Verify(m => m.FindAsync(1L), Times.Once());
完整的工作示例 here。下面用于验证的完整代码。
// Need package reference to Microsoft.EntityFrameworkCore v6.0.5
// Need package reference to Moq v4.18.1
using System;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Moq;
public class Program
{
public static async Task Main()
{
var todo = new TodoItem { Id = 1, Name = "test", IsComplete = true };
var mockSet = new Mock<DbSet<TodoItem>>();
mockSet.Setup(s => s.FindAsync(1L)).ReturnsAsync(todo);
var mockContext = new Mock<TodoContext>();
mockContext.Setup(c => c.TodoItems).Returns(mockSet.Object);
var service = new TodoItemsController(mockContext.Object);
var deleteTodo = await service.DeleteTodoItem(1);
mockSet.Verify(m => m.FindAsync(1L), Times.Once());
Console.WriteLine("Test complete without error");
}
}
public class TodoContext : DbContext
{
public virtual DbSet<TodoItem> TodoItems { get; set; }
}
public class TodoItem
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
}
public class TodoItemsController
{
readonly TodoContext _context;
public TodoItemsController(TodoContext context)
{
_context = context;
}
public async Task<IActionResult> DeleteTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
_context.TodoItems.Remove(todoItem);
await _context.SaveChangesAsync();
return NoContent();
}
public NotFoundResult NotFound() { return new NotFoundResult(); }
public NoContentResult NoContent() { return new NoContentResult(); }
}
public interface IActionResult{}
public class NotFoundResult : IActionResult {}
public class NoContentResult : IActionResult {}
请注意,此测试似乎不是特别有用。通常在单元测试时,我们会围绕结果进行断言,而不是围绕实现细节进行断言。无需断言 FindAsync 已被调用一次。它只会让测试更脆弱。如果您正在对操作方法进行单元测试,您需要确保在传入 non-existent 项目时获得 NotFoundResult,在传入现有项目时获得 NoContentResult,并且正确的项目从 DbSet 中删除。使用 in-memory DbContext 而不是模拟它可能会使事情变得更简单。