我在ASP.NET Core 2.0上有一个API,该API使用EF Core与MS SQL数据库集成。现在,我正在尝试使用NUnit和TestServer
为其设置集成/ api测试。问题是我需要将每个测试配置为“隔离的”,因此基本上它应该在自我之后清理(回滚)数据库。由于DB的复杂性,我无法使用补偿交易来获得理想的结果(很多遗留的东西需要考虑,例如触发器等)。
这里是我要测试的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());
}
但是由于某种原因,它不起作用(我可以在测试运行后看到数据库中的修改),我无法弄清楚。
为什么?我想念什么?
注意:两个测试版本均成功通过。
您缺少多个线程。 TransactionScope
使用线程本地存储。因此,您应该在一个线程上构造,使用和处置所有这些。引用documentation:
您还应该使用TransactionScope和DependentTransaction类,用于需要使用同一事务的应用程序跨多个函数调用或多个线程调用。
因此,如果要以线程安全的方式使用TransactionScope
,则需要使用DependentTransaction
。有关如何安全执行此操作的示例,请参见here。
编辑
[您也可以在构造范围时使用TransactionScopeAsyncFlowOption.Enabled
,这将防止TLS并允许范围流过异步/等待调用。
请注意默认值为TransactionScopeAsyncFlowOption.Suppress
。