【问题标题】:Unit testing in a legacy ASP.NET application to mock HttpContext在旧版 ASP.NET 应用程序中进行单元测试以模拟 HttpContext
【发布时间】:2016-04-28 06:38:17
【问题描述】:

我有一个方法:

public DataSet someMethod()
{
    List a = someObj.getList(name, integerId);
}

现在通过HttpContext.Current.Session 变量获取integerId。 我已经为该方法编写了一个单元测试。但是由于测试在 Web 进程之外运行,HttpContext.Current.Session 返回 null 并且测试失败。

有什么解决方法吗?

【问题讨论】:

  • 在经典的 ASP.NET 中模拟 HttpContext 很困难。我建议您将您对 HttpContext.Session 的引用包装在您自己的可模拟类中。
  • 数据访问层不应依赖 HttpContext 对象。
  • 是的,理想情况下应该是这样。但是,为了遵循这一点,我的项目中的大量代码必须重构。

标签: c# asp.net unit-testing httpcontext


【解决方案1】:

首先你必须初始化HttpContext.Current:

HttpContext.Current = new HttpContext(new HttpRequest("", "http://blabla.com", "") {},
                                      new HttpResponse(new StringWriter()));

然后你必须设置会话:(Necroskillz 在his blog 中解释了这样做的方法)

public static void SetFakeSession(this HttpContext httpContext)
{
    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 });
}

下面的 sn-p 显示了它是如何工作的:

[TestMethod]
public void TestMethod1()
{
    HttpContext.Current = new HttpContext(new HttpRequest("", "http://blabla.com", "") {},
                                          new HttpResponse(new StringWriter()));

    HttpContext.Current.SetFakeSession();

    HttpContext.Current.Session["foo"] = 1;

    Assert.AreEqual(1, HttpContext.Current.Session["foo"]);
}

【讨论】:

    【解决方案2】:

    我猜这个问题的懒惰答案是:学习使用dependency injection,但我会继续提供一些指导。

    该类不太可能需要 HttpContext 中的一切。您没有指定如何计算integerId,但假设它是当前SessionStateSessionId 的哈希值。你的班级真正需要的是某种方式来获得那个特定的SessionId;不需要是完整的HttpContext

    interface ICurrentSessionIdProvider
    {
         string SessionId { get; }
    }
    

    现在你的班级有:

    // Pull this from a constructor parameter so you can provide any implementation you want
    private readonly ICurrentSessionIdProvider _sessionIdProvider;
    
    public DataSet someMethod()
    {
         int integerId = _sessionIdProvider.SessionId.GetHashCode();
         List a = someObj.getList(name, integerId);
    }
    

    现在在您的单元测试中,模拟这种依赖关系变得微不足道。 Moq 和 AutoFixture 等工具甚至可以为您完成,现在您的生活变得轻松愉快等等。

    当然在实际应用中你要使用基于 HttpContext 的实现:

    class HttpContextCurrentSessionIdProvider : ICurrentSessionIdProvider   
    {
         // Pull this from a constructor parameter or however you see fit.
         private readonly HttpContext _httpContext;
    
         public SessionId => _httpContext.Current.Session.SessionId;
    }  
    

    【讨论】:

      【解决方案3】:

      您需要以某种方式为您的测试注入一个伪造的 HttpContext - 最好是 HttpContextBase,这是一个可模拟但其他方面相同的 API。

      您可以将HttpContext 包装在HttpContextWrapper 中以获取HttpContextBase

      阅读控制反转的各种技术,并找到适合您的技术。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-09-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-03-25
        • 2019-12-13
        • 2016-08-19
        相关资源
        最近更新 更多