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

时间:2017-07-01 13:37:49

标签: c# entity-framework unit-testing async-await synchronizationcontext

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

我们也尝试使用async \ await,因为我们有很多中继的库,它在Web应用程序中运行良好。但它在单元测试中失败,因为在线程返回到await块之后CallContext.SetData没有被恢复。 以下是该问题的简化示例:

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。并且它适用于Web应用程序,并且在单元测试中失败,因为SetData未共享且在ContextUtils.DbContext.SomeTable.First(...);行上DbContext为空。

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

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

有没有办法解决它而不改变代码本身只是为了让它适用于单元测试? EF DbContext应该存储在单元测试中而不将其作为参数传递并且能够使用async \ await?

由于

2 个答案:

答案 0 :(得分:4)

好的,这里有很多东西。

就个人而言,我会因使用CallContext.GetData作为HttpContext.Current的后备而感到怀疑,特别是因为您的代码使用了async。请考虑使用AsyncLocal<T>。但是,AsyncLocal<T>可能还需要序列化。

  

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

这里有几件事:

  1. 首先,您不应该在ASP.NET上使用Task.Run
  2. 使用Task.Run将阻止(非逻辑)调用上下文工作,以及HttpContext.Current。因此,我假设您的代码未在DbContext代码中访问Task.Run
  3. 听起来您最好选择使用我的AsyncContext。这个类最初是为异步单元测试而编写的(在单元测试框架支持异步单元测试之前)。您根本不需要更新代码;只需在单元测试中使用它:

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

答案 1 :(得分:0)

避免使用#include <iostream> // std::cout #include <string> // std::string, std::to_string int main () { std::string str = std::to_string(2002); for(auto c: str) std::cout << c << " "; std::cout << std::endl; return 0; } 。使用async void

使测试方法等待
Task

[TestMethod] public async Task Test() { ContextUtils.DbContext = new SomeDbContext(); using (ContextUtils.DbContext) { await DoSomeActions(); } } 在单元测试期间不可用,因为它与IIS绑定,在单元测试期间不存在。避免将代码紧密耦合到HttpContext将其视为第三方资源,并将其抽象出您可以控制的代码。它将使测试更容易维护和测试代码。考虑检查您当前的设计。