#asp.net-core #moq #xunit
#c# #asp.net-core #moq #xunit
Вопрос:
Я хочу знать, как использовать Moq для издевательства над моим ядром EF DbContext
, когда я использую DI для предоставления контекста моей базы данных контроллеру, как показано ниже:
public class RegisterController : Controller
{
private AppDbContext context;
public RegisterController(AppDbContext appDbContext)
{
context = appDbContext;
}
public IActionResult Create()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Create(Register register)
{
if (ModelState.IsValid)
{
context.Add(register);
await context.SaveChangesAsync();
return RedirectToAction("Read");
}
else
return View();
}
}
Вот AppDbContext
мой контекст базы данных для ядра EF.
Я хочу написать тестовый пример для Create
действия. Я попробовал приведенный ниже код:
[Fact]
public async Task Test_Create_POST_ValidModelState()
{
// Arrange
var r = new Register()
{
Id = 4,
Name = "Test Four",
Age = 59
};
var mockRepo = new Mock<AppDbContext>();
mockRepo.Setup(repo => repo.CreateAsync(It.IsAny<Register>()))
.Returns(Task.CompletedTask)
.Verifiable();
var controller = new RegisterController(mockRepo.Object);
// Act
var result = await controller.Create(r);
// Assert
var redirectToActionResult = Assert.IsType<RedirectToActionResult>(result);
Assert.Null(redirectToActionResult.ControllerName);
Assert.Equal("Read", redirectToActionResult.ActionName);
mockRepo.Verify();
}
Основная проблема здесь в том, что я не могу сделать:
var mockRepo = new Mock<AppDbContext>();
Я хочу следовать этому подходу, поскольку я уже добавил базу данных в памяти.
Обратите внимание, что я знаю, что есть другой способ тестирования с использованием шаблона репозитория. Что можно сделать, изменив действие create как:
public class RegisterController : Controller
{
private IRegisterRepository context;
public RegisterController(IRegisterRepository appDbContext)
{
context = appDbContext;
}
public IActionResult Create()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Create(Register register)
{
if (ModelState.IsValid)
{
await context.CreateAsync(register);
return RedirectToAction("Read");
}
else
return View();
}
}
Где интерфейс IRegisterRepository
и RegisterRepository.cs
коды:
public interface IRegisterRepository
{
Task CreateAsync(Register register);
}
public class RegisterRepository : IRegisterRepository
{
private readonly AppDbContext context;
public RegisterRepository(AppDbContext dbContext)
{
context = dbContext;
}
public Task CreateAsync(Register register)
{
context.Register.Add(register);
return context.SaveChangesAsync();
}
}
и код startup.cs, который добавляет его в качестве сервиса, является:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDbContext>(optionsBuilder => optionsBuilder.UseInMemoryDatabase("InMemoryDb"));
services.AddScoped<IRegisterRepository, RegisterRepository>();
services.AddControllersWithViews();
}
Я не хочу следовать этому подходу (шаблону репозитория). Я хочу подделать контекст базы данных напрямую, потому что я хочу напрямую вставить запись в действие create, поскольку контроллер получает объект контекста базы данных в своем конструкторе.
Итак, как написать поддельный контекст базы данных с использованием moq в этих методах тестирования здесь?
Комментарии:
1. Если вы не можете
new Mock<AppDbContext>()
и не хотите абстрагироватьсяAppDbContext
, то почему бы просто не развернуть фактическийAppDbContext
?2. Рассматривали ли вы возможность использования пакета InMemoryDatabase для тестирования этого?
3. @mxmissile как. Пожалуйста, напишите ответ, чтобы я мог понять.
4. @ShafiqJetha да, я использую базу данных в памяти.
5. Если вы уже используете базу данных в памяти, вам просто нужно создать экземпляр нового контекста БД с помощью конструктора, который указывает на эту базу данных. Затем введите данные и запустите тесты.
Ответ №1:
Не издевайтесь над DbContext, потому что тесты с издевательским DbContext не обеспечат разработчику качественную обратную связь.
Издевательский DbContext будет проверять только то, что вызывается какой-то метод, что превратит обслуживание кода (рефакторинг) в кошмар.
Вместо этого используйте поставщика в памяти или поставщика Sqlite с функцией «в памяти».
Комментарии:
1. Но я хочу использовать контекст базы данных для непосредственного ввода в контроллер, и действия контроллера будут затем напрямую использовать контекст базы данных для выполнения операций CRUD. Итак, в этом случае я хочу написать методы тестирования. Итак, как это сделать с moq?
2. Вам не нужно использовать Moq, вы можете ввести фактический DbContext, но зарегистрировать его у поставщика в памяти.
3. Спасибо. Я понял, что Moq не может быть использован в этом случае. Я должен напрямую использовать поставщика в памяти для проведения тестирования, как это сделано здесь — github.com/dotnet/EntityFramework.Docs/blob/master/samples/core /…
Ответ №2:
Есть несколько действительно полезных библиотек, которые поддерживают то, что вы ищете.
Вот две из моих любимых:
EntityFrameworkCore3Mock
Единственным предварительным условием является то, что вы должны определить свой DbSet
as virtual
.
public class AppDbContext: DbContext
{
public virtual DbSet<Entity> Entities { get; set; }
}
Тогда издевательство было бы таким простым:
var initialEntities = new[]
{
new Entity { ... },
new Entity { ... },
};
var dbContextMock = new DbContextMock<AppDbContext>(DummyOptions);
var usersDbSetMock = dbContextMock.CreateDbSetMock(x => x.Entities, initialEntities);
EntityFrameworkCore.Тестирование
Единственное отличие здесь в том, как вы инициализируете таблицу с данными:
var initialEntities = new[]
{
new Entity { ... },
new Entity { ... },
};
var dbContextMock = Create.MockedDbContextFor<AppDbContext>();
dbContextMock.Set<Entity>().AddRange(initialEntities);
dbContextMock.SaveChanges();
Комментарии:
1. есть ли способ обойтись без использования какой-либо из этих библиотек.
2. @yogihosting Почему вы хотите это сделать? Вам не нужно изобретать велосипед. Просто используйте инструменты, которые облегчают вашу жизнь.