我有使用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?
由于
答案 0 :(得分:4)
好的,这里有很多东西。
就个人而言,我会因使用CallContext.GetData
作为HttpContext.Current
的后备而感到怀疑,特别是因为您的代码使用了async
。请考虑使用AsyncLocal<T>
。但是,AsyncLocal<T>
可能还需要序列化。
我也看到了Stephen的AsyncEx库(https://github.com/StephenCleary/AsyncEx)有自己的SynchronizationContext,但它需要我更新我的代码并使用AsyncContext.Run而不是Task.Run,我试图避免代码更新仅用于单元测试。
这里有几件事:
Task.Run
。Task.Run
将阻止(非逻辑)调用上下文工作,以及HttpContext.Current
。因此,我假设您的代码未在DbContext
代码中访问Task.Run
。听起来您最好选择使用我的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
将其视为第三方资源,并将其抽象出您可以控制的代码。它将使测试更容易维护和测试代码。考虑检查您当前的设计。