TransactionScope不会在使用NUnit和TestServer进行集成测试的每次测试TearDown中回滚

问题描述 投票:1回答:1

场景

我在ASP.NET Core 2.0上有一个API,该API使用EF Core与MS SQL数据库集成。现在,我正在尝试使用NUnit和TestServer为其设置集成/ api测试。问题是我需要将每个测试配置为“隔离的”,因此基本上它应该在自我之后清理(回滚)数据库。由于DB的复杂性,我无法使用补偿交易来获得理想的结果(很多遗留的东西需要考虑,例如触发器等)。

SUT API设置

这里是我要测试的API的示例:

// GET api/values
[HttpGet]
public async Task<IActionResult> Get(string dataName1, string dataName2)
{
    using (var scope = CreateScope())
    {
        await _service.DoWork(dataName1);
        await _service.DoWork(dataName2);
        scope.Complete();
    }
    return Ok();
}

DoWork()方法基本上在给定传递参数的情况下查找实体,并增加其另一个属性。然后只需调用SaveChanges()CreateScope()是一个帮助程序方法,它返回TransactionScope的实例:

return new TransactionScope(
    TransactionScopeOption.Required,
    new TransactionOptions() { IsolationLevel = IsolationLevel.RepeatableRead },
    TransactionScopeAsyncFlowOption.Enabled
);

集成测试设置

[TestFixture]
[SingleThreaded]
[NonParallelizable]
public class TestTest
{
    private TransactionScope _scope;

    [Test]
    public async Task Test11()
    {
        _scope = CreateScope();
        var result = await Client.GetAsync("api/values?dataName1=Name1&dataName2=Name2");
        Assert.DoesNotThrow(() => result.EnsureSuccessStatusCode());

        _scope.Dispose();
        _scope = null;
    }
}

这里Client是通过利用HttpClient创建的Microsoft.AspNetCore.TestHost.TestServer的实例,并且CreateScope()方法实际上与API中的相同。这种简单的情况很好用-通过调用_scope.Dispose()成功回滚了我的SUT API所做的更改,并且DB返回到其“干净”状态。

问题

现在,我想将与范围的创建/回滚有关的逻辑移到我的测试方法之外,并将其放在SetUp / TearDown中,以便自动处理所有测试。

[SetUp]
public async Task SetupTest()
{
    _scope = TransactionHelper.CreateScope();
}

[TearDown]
public async Task TeardownTest()
{
    _scope.Dispose();
    _scope = null;
}

[Test]
public async Task Test11()
{
    var result = await OneTimeTestFixtureStartup.Client.GetAsync("api/values?dataName1=Name1&dataName2=Name2");
    Assert.DoesNotThrow(() => result.EnsureSuccessStatusCode());
}

但是由于某种原因,它不起作用(我可以在测试运行后看到数据库中的修改),我无法弄清楚。

为什么?我想念什么?

注意:两个测试版本均成功通过。

c# transactions nunit integration-testing asp.net-core-2.0
1个回答
0
投票

您缺少多个线程。 TransactionScope使用线程本地存储。因此,您应该在一个线程上构造,使用和处置所有这些。引用documentation

您还应该使用TransactionScope和DependentTransaction类,用于需要使用同一事务的应用程序跨多个函数调用或多个线程调用。

因此,如果要以线程安全的方式使用TransactionScope,则需要使用DependentTransaction。有关如何安全执行此操作的示例,请参见here

编辑

[您也可以在构造范围时使用TransactionScopeAsyncFlowOption.Enabled,这将防止TLS并允许范围流过异步/等待调用。

请注意默认值为TransactionScopeAsyncFlowOption.Suppress

© www.soinside.com 2019 - 2024. All rights reserved.