Издевательство над контекстом базы данных ядра EF с использованием Moq и xUnit?

#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 с функцией «в памяти».

Поставщик базы данных ядра EF в памяти

Использование SQLite для тестирования приложения ядра EF

Комментарии:

1. Но я хочу использовать контекст базы данных для непосредственного ввода в контроллер, и действия контроллера будут затем напрямую использовать контекст базы данных для выполнения операций CRUD. Итак, в этом случае я хочу написать методы тестирования. Итак, как это сделать с moq?

2. Вам не нужно использовать Moq, вы можете ввести фактический DbContext, но зарегистрировать его у поставщика в памяти.

3. Спасибо. Я понял, что Moq не может быть использован в этом случае. Я должен напрямую использовать поставщика в памяти для проведения тестирования, как это сделано здесь — github.com/dotnet/EntityFramework.Docs/blob/master/samples/core /…

Ответ №2:

Есть несколько действительно полезных библиотек, которые поддерживают то, что вы ищете.
Вот две из моих любимых:

EntityFrameworkCore3Mock

Репозиторий Github

Единственным предварительным условием является то, что вы должны определить свой 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.Тестирование

Репозиторий Github

Единственное отличие здесь в том, как вы инициализируете таблицу с данными:

 var initialEntities = new[]
{
    new Entity { ... },
    new Entity { ... },
};


var dbContextMock = Create.MockedDbContextFor<AppDbContext>();
dbContextMock.Set<Entity>().AddRange(initialEntities);
dbContextMock.SaveChanges();
 

Комментарии:

1. есть ли способ обойтись без использования какой-либо из этих библиотек.

2. @yogihosting Почему вы хотите это сделать? Вам не нужно изобретать велосипед. Просто используйте инструменты, которые облегчают вашу жизнь.