使用 ASP.NET Core 和 Entity Framework Core 进行集成测试 - 如何在每次测试时恢复数据库中的测试数据?
Posted
技术标签:
【中文标题】使用 ASP.NET Core 和 Entity Framework Core 进行集成测试 - 如何在每次测试时恢复数据库中的测试数据?【英文标题】:Integration Testing with ASP.NET Core and Entity Framework Core - How to Restore Test Data in Database Per Test? 【发布时间】:2020-06-16 00:47:33 【问题描述】:我是新的 .NET Core 用户。我正在尝试对使用 EF Core 来管理数据的 ASP.NET.Core WebAPI 执行集成测试。架构基于eShopContainers DDD 示例。我正在使用 Xunit 进行测试,并考虑了三种方法将在 docker-compose 堆栈中运行的数据库重置为运行每个测试之前的初始状态:
-
测试设置:使用测试数据创建和种子数据库,TearDown:删除数据库。
Test Setup:包含测试数据的种子数据库表,TearDown:清除测试数据。
Test Setup:创建事务,TearDown:中止事务。
被测系统使用 BeginTransaction() 在事务中执行创建/删除/更新操作。不确定 EF Core 是否可以使用嵌套事务,因此在测试时尝试执行选项 1 和 2。
下面的代码使用 WebApiTestFactory 作为派生自WebApplicationFactory
的类。这是作为类夹具提供的,用于创建用于测试的 TestServer 实例。
public class CoursesScenarios : CoursesScenarioBase, IClassFixture<WebApiTestFactory>, IDisposable
/// <summary>
/// With Xunit constructor gets run before every test
/// Code in the Dispose method gets run after every test completed
/// </summary>
public CoursesScenarios(WebApiTestFactory factory) : base(factory)
Console.WriteLine("CoursesScenario test setup...");
Server.Host
.MigrateDbContext<CourseContext>((context, services) =>
Console.WriteLine("Seeding lookup data");
var env = services.GetService<IWebHostEnvironment>();
var settings = services.GetService<IOptions<CourseSettings>>();
var logger = services.GetService<ILogger<CoursesContextSeed>>();
new CoursesContextSeed()
.SeedAsync(context, env, settings, logger)
.Wait();
);
// load the test data
Server.Host
.MigrateDbContext<CourseContext>((context, services) =>
Console.WriteLine("Seeding the test data");
var env = services.GetService<IWebHostEnvironment>();
var settings = services.GetService<IOptions<CourseSettings>>();
var logger = services.GetService<ILogger<CoursesContextTestSeed>>();
new CoursesContextTestSeed()
.SeedAsync(context, env, settings, logger)
.Wait();
);
foreach(Course c in Context.Courses)
Console.WriteLine($"Course c.Id");
// tests here
[Fact]
...
//
///
/// <summary>
/// This gets called after every test by XUnit
/// </summary>
///
public void Dispose()
// using (var command = Context.Database.GetDbConnection().CreateCommand())
//
// command.CommandText = @"DELETE FROM CourseManagement.Course;
// ALTER SEQUENCE coursemanagement.""unit_UnitID_seq"" RESTART WITH 1;";
// Context.Database.OpenConnection();
// int result = command.ExecuteNonQuery();
// Console.WriteLine($"result records deleted from course table");
// how to reload entities to match deleted state of rows in table?
// currently entities in memory are saved back to database
//
Context.Database.EnsureDeleted();
以下异常在服务器上引发并在测试期间收到。这是由于删除了数据库,所以连接丢失了。
Npgsql.NpgsqlException (0x80004005): Exception while connecting
---> System.Net.Sockets.SocketException (111): Connection refused
at Npgsql.NpgsqlConnector.Connect(NpgsqlTimeout timeout)
at Npgsql.NpgsqlConnector.Connect(NpgsqlTimeout timeout)
at Npgsql.NpgsqlConnector.RawOpen(NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
at Npgsql.NpgsqlConnector.Open(NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
at Npgsql.NpgsqlConnection.<>c__DisplayClass32_0.<<Open>g__OpenLong|0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at Npgsql.NpgsqlConnection.Open()
at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlDatabaseCreator.Exists()
at Microsoft.EntityFrameworkCore.Migrations.HistoryRepository.Exists()
at Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.Migrate(String targetMigration)
at Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.Migrate(DatabaseFacade databaseFacade)
at Microsoft.AspNetCore.Hosting.IWebHostExtensions.InvokeSeeder[TContext](Action`2 seeder, TContext context, IServiceProvider services) in /src/BuildingBlocks/WebHost.Customization/IWebHostExtensions.cs:line 76
at Microsoft.AspNetCore.Hosting.IWebHostExtensions.<>c__DisplayClass1_0`1.<MigrateDbContext>b__2() in /src/BuildingBlocks/WebHost.Customization/IWebHostExtensions.cs:line 54
at Polly.Policy.<>c__DisplayClass108_0.<Execute>b__0(Context ctx, CancellationToken ct)
at Polly.Policy.<>c__DisplayClass138_0.<Implementation>b__0(Context ctx, CancellationToken token)
at Polly.Retry.RetryEngine.Implementation[TResult](Func`3 action, Context context, CancellationToken cancellationToken, ExceptionPredicates shouldRetryExceptionPredicates, ResultPredicates`1 shouldRetryResultPredicates, Action`4 onRetry, Int32 permittedRetryCount, IEnumerable`1 sleepDurationsEnumerable, Func`4 sleepDurationProvider)
是否有人设法使用 EF Core 成功重置数据库状态以进行集成测试,无论是使用删除数据库方法还是种子/取消种子表状态?还尝试了种子数据/非种子数据方法,重置身份密钥并使用原始 SQL 查询删除记录,但随后在将 EF Core 与基础表中已删除行的状态重新同步时遇到了困难,EF Core 将它们视为新实体并添加他们回来了。也许最好的方法是使用测试数据 sql 脚本和 ADO.NET 来初始化测试数据状态?
【问题讨论】:
请查看github.com/v-zubritsky/Reseed 是否有帮助 【参考方案1】:某些操作,您可能希望在整个测试运行中执行一次(即数据库创建和删除)。
为此,您可以使用ICollectionFixture<T>
:
public class DatabaseFixture : IDisposable
public DatabaseFixture()
// ... initialize database and sample data
DbContext = ...
DbContext.Database.Migrate();
public void Dispose()
// ... clean up test data from the database ...
DbContext.Database.EnsureDeleted();
DbContext.Dispose();
public DbContext DbContext get; private set;
[CollectionDefinition(nameof(DatabaseCollection))]
public class DatabaseCollection : ICollectionFixture<DatabaseFixture>
// This class has no code, and is never created. Its purpose is simply
// to be the place to apply [CollectionDefinition] and all the
// ICollectionFixture<> interfaces.
[Collection(nameof(DatabaseCollection))]
public class DatabaseTestClass1
DatabaseFixture fixture;
public DatabaseTestClass1(DatabaseFixture fixture)
this.fixture = fixture;
[Collection(nameof(DatabaseCollection))]
public class DatabaseTestClass2
// ...
【讨论】:
【参考方案2】:已解决,通过使用每个测试的测试设置/拆卸而不是类夹具并重新启动表中 postgresql 的新默认标识列上的标识,如建议的 here。
using (context)
// could execute a DELETE statement via command on the course and units
// to improve performance, otherwise a DELETE command is issued for each entity?
context.Courses.RemoveRange(context.Courses);
context.Units.RemoveRange(context.Units);
await context.SaveChangesAsync();
using (var command = context.Database.GetDbConnection().CreateCommand())
command.CommandText = @"ALTER TABLE CourseManagement.Unit ALTER COLUMN ""UnitID"" RESTART WITH 1";
context.Database.OpenConnection();
await command.ExecuteNonQueryAsync();
_logger.LogInformation($"------ nameof(TestSeed) deseeded test data");
【讨论】:
以上是关于使用 ASP.NET Core 和 Entity Framework Core 进行集成测试 - 如何在每次测试时恢复数据库中的测试数据?的主要内容,如果未能解决你的问题,请参考以下文章
使用 ASP.NET Core, Entity Framework Core 和 ABP 创建N层Web应用 第二篇
这是如何使用 Entity Framework Core 和 ASP.NET Core MVC 2.2+ 和 3.0 创建数据传输对象 (DTO)
使用 ASP.NET Core 和 Entity Framework Core 进行集成测试 - 如何在每次测试时恢复数据库中的测试数据?
在 ASP.NET Core 中使用 Entity Framework 6