单元测试和HttpContext.Current

时间:2012-12-03 01:30:49

标签: asp.net-mvc testing

我希望在我参与的项目中使用单元测试。该解决方案有一个UI层,一个域逻辑层和数据库库,它们全部分隔成物理上分隔的dll,因此它现在非常标准。

在Domain层中,我们有一个使用

的类
new HttpContextWrapper(HttpContext.Current)

获取当前请求的上下文,以编译动态菜单的Url。正如预期的那样,在Web环境中运行时一切正常,因为总是设置HttpContext.Current。

然而,当我对控制器进行单元测试时,我点击了这行代码并获得了Null引用异常。

经过一些研究后,我有很多文章建议使用Mock创建一个Fake Http Context并在创建控制器时设置它,如下所示:

var httpContext = FakeHttpContext();
ControllerContext context = new ControllerContext(new RequestContext(httpContext, new RouteData()), controller);
controller.ControllerContext = context;

但是这仍然没有改变HttpContxt.Current,我仍然得到一个空引用异常。

我做错了什么/如何解决此异常?

6 个答案:

答案 0 :(得分:1)

你看过MvcContrib TestHelper了吗?我从未使用它,但我看到其他人已经成功了。这是一篇包含further examples的博文。

答案 1 :(得分:1)

您可以使用以下代码创建HttpContext.Current:

[SetUp]
public void Test_SetUp()
{
    var context = CreateHttpContext("index.aspx", "http://tempuri.org/index.aspx", null);
    var result = RunInstanceMethod(Thread.CurrentThread, "GetIllogicalCallContext", new object[] { });
    SetPrivateInstanceFieldValue(result, "m_HostContext", context);
}

私人方法:

private static HttpContext CreateHttpContext(string fileName, string url, string queryString)
    {
        var sb = new StringBuilder();
        var sw = new StringWriter(sb);
        var hres = new HttpResponse(sw);
        var hreq = new HttpRequest(fileName, url, queryString);
        var httpc = new HttpContext(hreq, hres);
        return httpc;
    }

    private static object RunInstanceMethod(object source, string method, object[] objParams)
    {
        var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
        var type = source.GetType();
        var m = type.GetMethod(method, flags);
        if (m == null)
        {
            throw new ArgumentException(string.Format("There is no method '{0}' for type '{1}'.", method, type));
        }

        var objRet = m.Invoke(source, objParams);
        return objRet;
    }

    public static void SetPrivateInstanceFieldValue(object source, string memberName, object value)
    {
        var field = source.GetType().GetField(memberName, BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance);
        if (field == null)
        {
            throw new ArgumentException(string.Format("Could not find the private instance field '{0}'", memberName));
        }

        field.SetValue(source, value);
    }

答案 2 :(得分:1)

您应该在域类中使用HttpContextBase,我建议将该域类注入您的控制器,因此在单元测试期间,您可以向被测控制器提供带有虚假httpcontext的域类实例

实施例

public class DomainClass
{
  private readonly HttpContextBase _httpContextBase;

  // unit testing
  public DomainClass(HttpContextBase httpContextBase)
  {
     _httpContextBase = httpContextBase;
  }

  public DomainClass()
  {
     _httpContextBase = new HttpContextWrapper(HttpContext.Current);
  }   
}

答案 3 :(得分:0)

您不能以这种方式使用HttpContext.Current。在我之前的一次演出中,为了解释这一点,我们必须创建一个单独的包装类并访问该属性。

使用静力学是单元测试中非常常见的问题。

根据您的更新请求:

public static class HttpContextContainer
{
     private static HttpContext _ctx;
     public static HttpContext Current 
     {
         get 
         { 
             if (_ctx == null) _ctx = HttpContext.Current;
             return _ctx;
         }
         set
         {
             _ctx = value;
         }
     }
}

然后在您的使用中,您可能会执行以下操作:

new HttpContextWrapper(HttpContextContainer.Current)

var httpContext = FakeHttpContext();
HttpContextContainer.Current = httpContext;
ControllerContext context = new ControllerContext(new RequestContext(httpContext, new RouteData()), controller);
controller.ControllerContext = context;

答案 4 :(得分:0)

  

在Domain层中,我们有一个使用new的类   HttpContextWrapper(HttpContext.Current)

我认为您可能遇到设计问题。

为什么域层需要了解GUI内容?

您可能希望在控制器构造函数上创建一个参数来接受IHttpContextWrapper,这样您就可以传入模拟的包装器。像这样:

public MyController()
  :this(new HttpContextWrapper())
{
  ....
}

public MyController(IHttpContextWrapper contextWrapper)
{
  ....
}

看起来像

的测试
var context = new Mock<IHttpContextWrapper>();
// setup context
var controller = new MyController(context.Object);
....

然后更改控制器以传递域层所需的任何信息。

答案 5 :(得分:0)

警示故事

警惕使用 MS Fakes 来弥补 HttpContext.Current { get; }

调用 HttpContext.Current { set; } 会成功,但设置的值将不可用。

我们有人在基于 MSTest 的测试中使用 [TestInitialize] 进行了设置。

ShimHttpContext.CurrentGet = () => new ShimHttpContext() 
{ 
  SessionGet = () => new ShimHttpSessionState() 
  { 
    ItemGetString = (name) => 
    { 
      return null; 
    } 
  } 
};