【问题标题】:Integration Testing with ASP.NET Core and Entity Framework Core - How to Restore Test Data in Database Per Test?使用 ASP.NET Core 和 Entity Framework Core 进行集成测试 - 如何在每次测试时恢复数据库中的测试数据?
【发布时间】:2020-06-16 00:47:33
【问题描述】:

我是新的 .NET Core 用户。我正在尝试对使用 EF Core 来管理数据的 ASP.NET.Core WebAPI 执行集成测试。架构基于eShopContainers DDD 示例。我正在使用 Xunit 进行测试,并考虑了三种方法将在 docker-compose 堆栈中运行的数据库重置为运行每个测试之前的初始状态:

  1. 测试设置:使用测试数据创建和种子数据库,TearDown:删除数据库。
  2. Test Setup:包含测试数据的种子数据库表,TearDown:清除测试数据。
  3. 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 来初始化测试数据状态?

【问题讨论】:

标签: c# asp.net-core entity-framework-core


【解决方案1】:

某些操作,您可能希望在整个测试运行中执行一次(即数据库创建和删除)。

为此,您可以使用ICollectionFixture&lt;T&gt;

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");
    }
    

    【讨论】:

      猜你喜欢
      • 2018-05-24
      • 1970-01-01
      • 2019-09-12
      • 2020-04-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-10
      • 1970-01-01
      相关资源
      最近更新 更多