Я пытался в течение нескольких дней, чтобы проверить метод удаления, но безуспешно.
После отладки кода я понял, что проблема в Метод 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());
}
На данный момент мое приложение использует память в качестве базы данных, и примеры, которые я видел, выглядели так:
Я думаю, что ваш последний макет (на mockContext) должен быть на mockSet в методе FindAsync.
Это все еще не меняет того факта, что вы должны использовать один или другой. Нет смысла использовать базу данных в памяти, если вы собираетесь выполнять вызовы Moq. И наоборот: нет смысла имитировать вызовы методов, если вы собираетесь использовать БД в памяти. Выберите один или другой.
Я попытался сделать это, как показано ниже, но все равно возвращает ноль: ` var mockSet = new Mock<DbSet<TodoItem>>(); mockSet.Setup(c => c.FindAsync(1)).ReturnsAsync(todo); `
Я понимаю вашу точку зрения @mason, и локально я уже удалил часть базы данных (... UseInMemory (...)), но в любом случае Финдасинк по-прежнему возвращает значение null
Я превратил превратил ваш код в правильный минимальный воспроизводимый пример. Это гораздо полезнее, потому что теперь любой может добавить его в новое консольное приложение или dotnetfiddle и получить точно такую же ошибку, как и вы.
Спасибо за попытку помочь мне, @mason. Оценив ваш код и появившуюся ошибку, я заметил, что ваш DbSet не соответствует virtual
, что является директивой Moq. И еще раз спасибо, что нашли время для разработки примера, и я извиняюсь за то, что не сделал этого, как вы могли заметить, я здесь новичок, и я ценю ваш совет: D
Вы настраиваете макет и проверяете макет для 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 в памяти, а не насмешка над ним, вероятно, сделает это проще.
большое спасибо за помощь @mason, спас мою учебу здесь
Почему вы комбинируете Moq с базой данных в памяти? Вам не нужны оба. Только одно или другое.