Изоляция базы данных с помощью поставщика EF Core в памяти¶
В этом разделе рассмотрим, как писать тесты для кода, который полагается на экземпляр класса DbContext от EF Core. Мы разберем, как создать базу данных в памяти, какова разница между поставщиком EF в памяти и поставщиком SQLite в памяти, а также, как использовать поставщик SQLite для создания быстрых изолированных тестов.
Как уже говорилось, EF Core — это инструмент объектно-реляционного отображения, который используется в основном с реляционными БД.
В следующем примере показана сильно урезанная версия класса RecipeService из приложения с рецептами, показывающая единственный способ получить подробную информацию о рецепте:
public class RecipeService
{
readonly AppDbContext _context;
public RecipeService(AppDbContext context)
{
_context = context;
}
public RecipeModel GetRecipe(int id)
{
return _context.Recipes
.Where(x => x.RecipeId == id)
.Select(x => new RecipeViewModel
{
Id = x.RecipeId,
Name = x.Name
})
.SingleOrDefault();
}
}
Для того, чтобы протестировать этот код, нужен тестовый поставщик, чтобы избавиться от необходмости использовать реальную БД в тестах.
У Microsoft есть два предназначенных для этих целей поставщика БД в памяти, а именно:
- Microsoft.EntityFrameworkCore.InMemory — этот поставщик хранит объекты непосредственно в памяти. При этом он не является реляционной БД, не соблюдает ограничения (constraints) и к ней нельзя выполнить настоящий SQL-запрос. Но при этом работает быстро;
- Microsoft.EntityFrameworkCore.Sqlite — реляционная БД SQLite. Обычно она пишет в файл, но у поставщика есть режим in-memory.
Вот как работают оба этих поставщика (в сравнении с Sql Server).

Далее будем использовать поставщик SQLite.
Прежде всего нужно добавить пакет Microsoft.EntityFrameworkCore.Sqlite в тестовый проект, который добавит метод расширения UseSqlite().
Далее мы создаём объект SqliteConnection и используем строку подключения "DataSource=:memory:", после чего открываем соединение.
Предупреждение
База данных в памяти уничтожается, когда соединение закрыто. Если соединение не открыть самостоятельно, EF Core закроет его при удалении DbContext, поэтому, если мы хотим использовать БД несколькими экземплярами DbContext, нужно открывать соединение явно.
[Fact]
public void GetRecipeDetails_CanLoadFromContext()
{
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseSqlite(connection)
.Options;
using(var context = new AppDbContext(options))
{
context.Database.EnsureCreated();
context.Recipes.AddRange(
new Recipe { RecipeId = 1, Name = "Recipe1" },
new Recipe { RecipeId = 2, Name = "Recipe2" },
new Recipe { RecipeId = 3, Name = "Recipe3" });
context.SaveChanges();
}
using (var context = new AppDbContext(options))
{
var service = new RecipeService(context);
var recipe = service.GetRecipe(id: 2);
Assert.NotNull(recipe);
Assert.Equal(2, recipe.Id);
Assert.Equal("Recipe2", recipe.Name);
}
}
Вот типичные шаги для тестирования класса, зависящего от DbContext:
- создайте
SqliteConnectionсо строкой подключения"DataSource=:memory:"и откройте соединение; - создайте
DbContextOptionsBuilder<>и вызовите методUseSqlite(), передавая открытое соединение; - извлеките объект
DbContextOptionsи свойстваOptions; - передайте параметры экземпляру
DbContextи убедитесь, что БД соответствует модели EF Core при помощиcontext.Database.EnsureCreated(). Это действие аналогично запуску миграций в БД, но его следует выполнять только для тестовых БД. Создайте и добавьте необходимые тестовые данные и вызовите методSaveChanges(), чтобы сохранить их; - Создайте новый экземпляр
DbContextи внедрите его в тестируемый класс.
Использование двух отдельных экземпляров DbContext позволяет избежать ошибок в тестах из-за кэширования данных EF Core. При такмо подходе можно быть уверенным, что мы считали данные, сохраненные в БД.
Дата создания : 25 ноября 2022 г.