简体   繁体   English

Moq FindAsync 方法返回 null

[英]Moq FindAsync method returns null

I've been trying for a few days to test a deletion method, but without success.我已经尝试了几天来测试删除方法,但没有成功。

After debugging the code I realized that the problem is in the FindAsync method returns null and this causes the test to fall into the NotFound() condition.调试代码后,我意识到问题在于FindAsync 方法返回 null ,这导致测试陷入NotFound()条件。

As I'm new to the world of C#, .NET, EntityFramework, and Moq, could anyone help me?由于我是 C#、.NET、EntityFramework 和 Moq 世界的新手,任何人都可以帮助我吗?

  • Controller控制器
        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();
        }
  • Test测试
    [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());
        
        
    }

You are setting up a mock and verifying a mock for FindAsync on an int, when your controller is passing it a long.当您的控制器长时间传递它时,您正在为 int 上的 FindAsync 设置一个模拟并验证模拟。 Therefore you need to set up your mock and verification in a long, not an int.因此,您需要在 long 而不是 int 中设置模拟和验证。 And also set up the FindAsync mock on the DbSet, not on the DbContext.并且还在 DbSet 上设置 FindAsync 模拟,而不是在 DbContext 上。

For example:例如:

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());

Complete working sample here .这里完成工作示例。 Full code used to verify below.用于验证的完整代码如下。

// 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 {}

Note that this test doesn't appear particularly useful.请注意,此测试似乎不是特别有用。 Generally when unit testing, we make assertions around the result, not assertions around the implementation details.通常在单元测试时,我们围绕结果进行断言,而不是围绕实现细节进行断言。 There's no need to make an assert that FindAsync was called once.无需断言 FindAsync 被调用过一次。 It just makes the test more brittle.它只会使测试更加脆弱。 If you're unit testing the action method, you'd want to make sure that you get a NotFoundResult when you pass in a non-existent item, and a NoContentResult when you pass in an existent item, and that the proper item is removed from the DbSet.如果您正在对 action 方法进行单元测试,您需要确保在传入不存在的项目时获得 NotFoundResult,在传入现有项目时获得 NoContentResult,并且正确的项目被删除来自 DbSet。 Using an in-memory DbContext rather than mocking it will probably make that simpler.使用内存中的 DbContext 而不是模拟它可能会更简单。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM