2

我希望在我参与的项目中使用单元测试。该解决方案有一个 UI 层、一个域逻辑层和数据库基础,所有这些都分离成物理分离的 dll,所以它现在非常标准。

在域层中,我们有一个类使用

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 并且我仍然得到一个空引用异常。

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

4

6 回答 6

2

您可以使用以下代码创建 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);
    }
于 2012-12-03T03:20:34.533 回答
1

你看过MvcContrib TestHelper吗?我从未使用过它,但我看到其他人已经成功使用它。这是一篇包含更多示例的博客文章。

于 2012-12-03T02:52:44.733 回答
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);
  }   
}
于 2012-12-03T15:19:00.353 回答
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;
于 2012-12-03T01:35:13.137 回答
0

在域层中,我们有一个使用 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);
....

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

于 2012-12-03T01:46:26.240 回答
0

警示故事

警惕使用 MS Fakes 来填充HttpContext.Current { get; }

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

我们有人[TestInitialize]在基于 MSTest 的测试中设置了这个。

ShimHttpContext.CurrentGet = () => new ShimHttpContext() 
{ 
  SessionGet = () => new ShimHttpSessionState() 
  { 
    ItemGetString = (name) => 
    { 
      return null; 
    } 
  } 
}; 
于 2021-01-06T16:35:58.527 回答