ASP.NET Core集成测试:每个测试后回滚事务

3

我已经在我的集成测试中使用了这份文档:asp.net core 集成测试,以下是我的简化版本集成测试:

 public class ApplicationControllerTests : IClassFixture<CustomWebApplicationFactory<Startup>>
 {
     private readonly CustomWebApplicationFactory<Startup> _factory;

     public ApplicationControllerTests(CustomWebApplicationFactory<Startup> factory)
     {
           _factory = factory;
     }

    [Fact]
    public async Task AcceptOffer_WhenCalled_ShouldReturnSuccess()
    {
        var httpClient = factory.CreateClient();

        var acceptOfferRequest = new AcceptOfferRequest
        {
            OfferId = 1,
            OfferType = 1
        }.ToJsonStringContent();

        var response = await httpClient.PostAsync("/api/v1/Application/AcceptOffer", acceptOfferRequest);

        response.EnsureSuccess();

        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
    }
}

正如您所见,我正在向“/api/v1/Application/AcceptOffer” API发送HTTP请求,该API会更新数据库中的一些实体,并在一切正常时返回状态码200。
测试后,我想将数据库恢复到测试之前的状态。 为此,我希望将我的测试放在事务中,并在每个测试完成后回滚。 根据当前的实现,这是否可能?因为删除并重新创建数据库是代价高昂的,这就是为什么我想将我的测试放在事务中的原因。
我正在使用MS SQL数据库和Entity Framework ORM。
一种解决方案是使用DbContext的singleton,然后在事务中包装所有测试,但问题是SQL不支持嵌套事务,如果API也使用事务,它将抛出运行时异常。
另一种解决方案是使用C#的TransactionScope,但它不适用于SQL事务,并且不适用于不同的线程。

你可以使用内存数据库提供程序。 - Ian Kemp
3
我更喜欢生产数据库的完全副本。内存数据库可能有不同的工作方式。 - pfloyd
你的CustomWebApplicationFactory里面有什么内容?你是否覆盖了DbContext设置(Entity Framework)中的任何内容,还是使用了其他ORM? - kristofke
我正在使用Entity Framework,并且没有覆盖任何内容。只是更改托管环境以读取不同的应用程序设置。 - pfloyd
一个解决方案是使用 DbContext 的单例,然后将所有测试包装在事务中。但问题是 SQL 不支持嵌套事务,而 C# 的 TransactionScope 在不同线程上无法工作。 - pfloyd
使用拆除过程。不要为Dbcontext使用单例模式。 - maxspan
2个回答

0
好的,我发现使用事务来解决这个问题没有什么好的方法或可能性。所以我写了全局脚本,在每次测试后手动清空所有表并执行它。显然,这比删除和重新创建数据库要快。

你可以扩展DbContext并重写SaveChanges方法... - Jeremy Lakeman
你可以使用“重生”代替。 - Ghassen

0

我认为在不对应用程序本身进行任何调整的情况下,无法将数据库事务从测试传递到 Web 应用程序。除非 TestServer 在同一进程中托管应用程序可能会有所帮助。

一个可能的想法(虽然我不确定是否喜欢它)涉及以下应用程序更改:

  • 在您的应用程序中引入专门的仅供测试使用的端点,该端点将接受标识符(例如 guid),启动事务并通过标识符将该事务保存在应用程序状态中;
  • 将此事务标识符附加到测试中执行的每个请求,并编写一个中间件,该中间件将从应用程序状态检索现有事务并将其附加到请求的 DbContext;
  • 引入另一个端点以通过其 ID 恢复事务;
  • 确保所有这些逻辑仅适用于测试环境。

如果您决定尝试它,我建议您检查 TestServer 的工作方式,以及是否可以以某种方式使用它来实现此相同的想法,而无需引入专用端点来创建/回滚事务。

而且,您是正确的,无法使用此方法来测试需要自己具有事务的逻辑,因为通常无法嵌套事务。因此,优化仅适用于测试的一部分,其余部分需要以不同的方式执行清理。

但如果我是你,我会选择使用其他恢复数据库的方法,而不是使用事务。比如像数据库快照或插入/删除脚本这样的方式 - 这正是你实际采取的路线。

话虽如此,我建议你去看看我正在开发的Reseed库,它能够通过生成插入和删除脚本并快速执行来处理数据库还原。还有Respawn库可用于数据库清理,也适合你的情况。


更新

根据这个问题和回复,使用TransactionScope类型可以实现非常接近所需行为的行为。它与TestServer一起按预期工作,但正如主题发起人已经提到的那样,它也有缺点。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接