c# - 等待后 HttpContext.Current 为空(仅在单元测试中)

标签 c# asp.net-mvc unit-testing async-await

我正在为 MVC 5 网络应用程序编写单元测试。我在测试中模拟了 HttpContext.Current。 当运行以下代码形成测试 httpSessionStateAfter throw

System.AggregateException : One or more errors occurred.
----> System.NullReferenceException : Object reference not set to an instance of an object.

只有在我运行单元测试时才会发生这种情况。当应用程序运行时这项工作正常。 我将 Nunit 2.6.3 与 reshaper 测试运行器一起使用。

 var httpSessionStateBefour = System.Web.HttpContext.Current.Session;
 var Person= await Db.Persons.FirstOrDefaultAsync();
 var httpSessionStateAfter = System.Web.HttpContext.Current.Session;

如何解决这个问题?

这就是我模拟 HttpContext 的方式

 HttpContext.Current = Fakes.FakeHttpContext();            
 HttpContext.Current.Session.Add("IsUserSiteAdmin", true);
 HttpContext.Current.Session.Add("CurrentSite", null);

public static class Fakes
{
    public static HttpContext FakeHttpContext()
    {
        var httpRequest = new HttpRequest("", "http://stackoverflow/", "");
        var stringWriter = new StringWriter();
        var httpResponce = new HttpResponse(stringWriter);
        var httpContext = new HttpContext(httpRequest, httpResponce);

        var sessionContainer = new HttpSessionStateContainer("id", new SessionStateItemCollection(),
            new HttpStaticObjectsCollection(), 10, true,
            HttpCookieMode.AutoDetect,
            SessionStateMode.InProc, false);

        httpContext.Items["AspSession"] = typeof (HttpSessionState).GetConstructor(
            BindingFlags.NonPublic | BindingFlags.Instance,
            null, CallingConventions.Standard,
            new[] {typeof (HttpSessionStateContainer)},
            null)
            .Invoke(new object[] {sessionContainer});

        return httpContext;
    }
}

最佳答案

首先,我确实建议您尽可能将代码与 HttpContext.Current 隔离;这不仅会使您的代码更易于测试,而且有助于您为 ASP.NET vNext 做好准备,它更像 OWIN(没有 HttpContext.Current)。

但是,这可能需要进行大量更改,而您可能还没有做好准备。要正确模拟 HttpContext.Current,您需要了解它的工作原理。

HttpContext.Current 是一个由 ASP.NET SynchronizationContext 控制的每线程变量。这个SynchronizationContext是一个“请求上下文”,代表当前的请求;它是在收到新请求时由 ASP.NET 创建的。我有一个 MSDN article on SynchronizationContext如果您对更多细节感兴趣。

正如我在 async intro blog post 中解释的那样,当您 await 一个 Task 时,默认情况下它将捕获当前的“上下文”并使用它来恢复 async 方法。当 async 方法在 ASP.NET 请求上下文中运行时,await 捕获的“上下文”是 ASP.NET SynchronizationContext。当 async 方法恢复时(可能在不同的线程上),ASP.NET SynchronizationContext 将在恢复 之前设置 HttpContext.Current异步 方法。这就是 async/await 在 ASP.NET 主机中的工作方式。

现在,当您在单元测试中运行相同的代码时,行为会有所不同。具体来说,没有 ASP.NET SynchronizationContext 来设置 HttpContext.Current。我假设您的单元测试方法返回 Task,在这种情况下,NUnit 根本不提供 SynchronizationContext。因此,当 async 方法恢复时(可能在不同的线程上),它的 HttpContext.Current 可能不是同一个。

有几种不同的方法可以解决这个问题。一种选择是编写您自己的 SynchronizationContext 来保留 HttpContext.Current,就像 ASP.NET 一样。一个更简单(但效率较低)的选项是使用 a SynchronizationContext that I wrote called AsyncContext ,这确保 async 方法将在同一线程上恢复。您应该能够安装 my AsyncEx library from NuGet然后将您的单元测试方法包装在对 AsyncContext.Run 的调用中。请注意,单元测试方法现在是同步的:

[Test]
public void MyTest()
{
  AsyncContext.Run(async () =>
  {
    // Test logic goes here: set HttpContext.Current, etc.
  });
}

关于c# - 等待后 HttpContext.Current 为空(仅在单元测试中),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26118756/

相关文章:

c# - php lib memcached 奇怪的输入值

c# - 为什么 ServicePointManager.SecurityProtocol 默认值在不同机器上不同?

html - Chrome 在使用 jQuery ajax 时删除表单元素

asp.net-mvc - MVC 层和 DAL 和数据层

javascript - 未找到 Google 扩展 native 消息传递主机

c# - 如何在控制台应用程序中设置默认连接字符串?

c# - Request.UrlReferrer 为空?

unit-testing - MsTest 不支持在测试基类中定义 TestMethod 吗?

c# - 如何使用 FakeItEasy 验证 FindOneAndUpdateAsync 方法针对伪造的 MongoCollection 运行?

javascript - 如何使用 npm 将自定义模块导入 babel-jest 测试?