等待单元测试和 EntityFramework 存储在 CallContext.SetData

Await in Unit tests and EntityFramework stored in CallContext.SetData

我有一个使用 EntityFramework 上下文的 MVC 网络应用程序,并将其存储在 HttpContext.Current.Items 中。当 HttpContext.Current 不可用时,它使用 CallContext.SetData 将数据存储在当前线程存储中。 HttpContext 用于 Web 应用程序本身,CallContext 用于单元测试以在其中存储相同的 EF DbContext。

我们也在尝试使用 async\await,因为我们有很多依赖它们的库,而且它在网络应用程序中运行良好。但它在单元测试中失败,因为 CallContext.SetData 在线程 returns 等待块后未恢复。 这是问题的简化示例:

public async Task Test()
{
    ContextUtils.DbContext = new SomeDbContext();
    using (ContextUtils.DbContext){
        await DoSomeActions();
    }
}

public async Task DoSomeActions(){

    var data = await new HttpClient().GetAsync(somePage);

    // on next line code would fail as ContextUtils.DbContext is null
    // as it wasn't synced to new thread that took it
    var dbData = ContextUtils.DbContext.SomeTable.First(...);
}

所以在那个例子中 ContextUtils.DbContext 基本上设置了 HttpContext\CallContext.SetData。它适用于网络应用程序,并且在单元测试中失败,因为 SetData 未共享并且在 ContextUtils.DbContext.SomeTable.First(...); 行 DbContext 为空。

我知道我们可以使用 CallContext.LogicalSetData\LogicalGetData 并且它会与 ExecutionContext 共享,但它要求项目是可序列化的,我不想用序列化属性标记 DbContext 来尝试序列化它。

我还看到了 Stephen 的 AsyncEx 库 (https://github.com/StephenCleary/AsyncEx),它有自己的 SynchronizationContext,但它需要我更新我的代码并使用 AsyncContext.Run 而不是 Task.Run,我'我试图避免仅为单元测试更新代码。

有没有办法在不更改代码本身的情况下修复它以使其适用于单元测试?并且 EF DbContext 应该存储在单元测试中而不将其作为参数传递并且能够使用 async\await?

谢谢

避免使用 async void。使用 Task

使测试方法可等待
[TestMethod]
public async Task Test() {
    ContextUtils.DbContext = new SomeDbContext();
    using (ContextUtils.DbContext) {
        await DoSomeActions();
    }
}

HttpContext 在单元测试期间不可用,因为它绑定到在单元测试期间不存在的 IIS。避免将您的代码与 HttpContext 紧密耦合,将其视为第 3 方资源,并将其抽象到您可以控制的代码后面。这将使测试维护和测试代码变得更加容易。考虑检查您当前的设计。

好的,这里有很多东西。

就我个人而言,我会怀疑使用 CallContext.GetData 作为对 HttpContext.Current 的回退,尤其是因为您的代码使用了 async。考虑改用 AsyncLocal<T>。但是,AsyncLocal<T> 也可能需要序列化。

I also saw Stephen's AsyncEx library (https://github.com/StephenCleary/AsyncEx) that has own SynchronizationContext, but it would require me to update my code and use AsyncContext.Run instead of Task.Run, and i'm trying to avoid code updating just for unit tests.

这里有几件事:

  1. 你一开始就不应该在 ASP.NET 上使用 Task.Run
  2. 使用 Task.Run 将阻止(非逻辑)调用上下文以及 HttpContext.Current 工作。所以我假设您的代码没有从 Task.Run 代码中访问 DbContext

听起来你最好的选择是使用我的AsyncContext。这个 class 最初是为异步单元测试编写的(早在单元测试框架支持异步单元测试之前)。您根本不需要更新代码;只需在单元测试中使用它:

public void Test()
{
  AsyncContext.Run(async () =>
  {
    ContextUtils.DbContext = new SomeDbContext();
    using (ContextUtils.DbContext)
    {
      await DoSomeActions();
    }
  });
}