【问题标题】:Mock HttpContext for unit testing a .NET core MVC controller?用于单元测试 .NET 核心 MVC 控制器的模拟 HttpContext?
【发布时间】:2017-05-14 23:29:21
【问题描述】:

我在控制器中有一个函数,我正在对它进行单元测试,该函数需要 http 请求标头中的值。我无法初始化 HttpContext,因为它是只读的。

我的控制器函数需要“device-id”的 http 请求标头值

[TestMethod]
public void TestValuesController()
{
    ValuesController controller = new ValuesController();

    //not valid controller.HttpContext is readonly
    //controller.HttpContext = new DefaultHttpContext(); 

    var result = controller.Get();
    Assert.AreEqual(result.Count(), 2);
}

有没有不使用第三方库的直接方法来做到这一点?

【问题讨论】:

  • 不要使用HttpContext?使用控制器的全部意义在于数据来自控制器的参数。如果您的控制器使用 HttpContext 来读取数据,就好像它是一个 WebForms 页面一样,那么您就有问题了。
  • @PanagiotisKanavos 标头中的值是一条信息,指示呼叫来自哪个移动设备。这是检索正确数据所必需的。设备 ID 位于标头中,因为身份验证需要该 ID,由自定义操作过滤器处理。我可以将设备 ID 作为路由参数传递,但这将是多余的
  • 检查 FromHeaderAttribute 但也检查重复项。 HttpContext 现在可以通过配置注入了
  • 我建议您编辑您的问题以准确指定您想要的内容(访问标题字段以识别移动设备)。 ASP.NET 文档似乎正在经历一个....“过渡”时期,说得好听点,缺少文档页面。检查询问如何路由移动设备的this almost identical question

标签: c# asp.net-mvc unit-testing asp.net-core


【解决方案1】:

我能够以这种方式初始化 httpcontext 和 header:

[TestMethod]
public void TestValuesController()
{
    ValuesController controller = new ValuesController();
    controller.ControllerContext = new ControllerContext();
    controller.ControllerContext.HttpContext = new DefaultHttpContext();
    controller.ControllerContext.HttpContext.Request.Headers["device-id"] = "20317";
    var result = controller.Get();
    //the controller correctly receives the http header key value pair device-id:20317
    ...
}

【讨论】:

    【解决方案2】:

    与其模拟 HTTPContext,不如将标头映射到方法的参数中可能是一个更好的主意。例如,在这个答案底部的控制器中,id 参数被设置为名称等于“device-id”的值头......然后单元测试变为

    [TestMethod]
    public void TestValuesController()
    {
        ValuesController controller = new ValuesController();
        var result = controller.GetHeaderValue("27");
        Assert.AreEqual(result, "27");
    }
    

    虽然您可以模拟 HttpContext,但在我看来,除非您别无选择,否则应该避免这种情况。 FromHeaderAttribute 的文档可以在这里找到FromHeaderAttribute Class

    public class ValuesController: Controller
    {
        public string GetHeaderValue([FromHeader(Name = "device-id")] string id)
        {
            return id;
        }
    }
    

    【讨论】:

    • 在我的情况下,IIRC,要求将其包含在 http 标头中,因为需要在 .net 核心中间件组件中评估相同的值
    【解决方案3】:

    对于在 HttpContext 中需要标头但还需要其他数据的人,您可以通过使用 features 初始化上下文来做到这一点,这要归功于 DefaultHttpContext 的第二个构造函数类:

    1。使用您需要的标题创建一个标题字典:

    var headers = new Dictionary<string, StringValues>
    {
       { "myHeaderKey", "myHeaderValue" },
    };
    var headerDictionary = new HeaderDictionary(headers)
    

    2。使用之前创建的头字典创建一个 HttpRequestFeature:

    var requestFeature = new HttpRequestFeature()
    {
        Headers = headerDictionary,
    };
    

    3。创建一个包含先前创建的特征的特征集合:

     var features = new FeatureCollection();
    
    features.Set<IHttpRequestFeature>(requestFeature);
    

    4。使用特征集合初始化 DefaultHttpContext,并将其设置为控制器的 HttpContext:

    var httpContext = new DefaultHttpContext(features);
    
    var controller = new MyController();
    controller.ControllerContext = new ControllerContext();
    controller.ControllerContext.HttpContext = httpContext;
    

    控制器的上下文将设置正确的标头,您仍然可以通过在实例化 DefaultHttpContext 之前为 featureCollection 设置额外的 HttpContext 属性来根据需要为上下文提供更多数据(例如,查询字符串的feature.Set&lt;IQueryFeature&gt;(new QueryFeature(...)))。

    PS:有关使用功能模拟(以及一般的单元测试)HttpContext 的更深入解释,请参阅:https://weblogs.asp.net/ricardoperes/unit-testing-the-httpcontext-in-controllers

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-06-13
      • 2011-01-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多