Метод Moq FindAsync возвращает значение null

Я пытался в течение нескольких дней, чтобы проверить метод удаления, но безуспешно.

После отладки кода я понял, что проблема в Метод FindAsync возвращает значение 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());
        
        
    }

🤔 А знаете ли вы, что...
С C# легко создавать графические приложения с помощью Windows Forms и WPF.


20
1

Ответ:

Решено

Вы настраиваете макет и проверяете макет для FindAsync на int, когда ваш контроллер передает его long. Поэтому вам нужно настроить макет и проверку в формате long, а не int. А также настройте макет FindAsync для DbSet, а не для 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());

Полный рабочий образец здесь. Полный код, используемый для проверки ниже.

// 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 был вызван один раз. Это только делает тест более хрупким. Если вы выполняете модульное тестирование метода действия, вам нужно убедиться, что вы получаете NotFoundResult при передаче несуществующего элемента и NoContentResult при передаче существующего элемента, а соответствующий элемент удаляется. из DbSet. Использование DbContext в памяти, а не насмешка над ним, вероятно, сделает это проще.