用于单元测试 .NET 核心 MVC 控制器的模拟 HttpContext?

Posted

技术标签:

【中文标题】用于单元测试 .NET 核心 MVC 控制器的模拟 HttpContext?【英文标题】:Mock HttpContext for unit testing a .NET core MVC controller? 【发布时间】: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 【参考方案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

【讨论】:

以上是关于用于单元测试 .NET 核心 MVC 控制器的模拟 HttpContext?的主要内容,如果未能解决你的问题,请参考以下文章

asp.net mvc中的单元测试问题?

在 ASP.NET Core MVC API 控制器上对 AuthorizeAttribute 进行单元测试

RedirectToAction 不适用于 Tempdata - ASP.net 核心 MVC

在测试控制器方法时,Spring MVC 4.2.6 版似乎没有将模拟服务注入控制器

用于单元测试的模拟休息模板

我应该测试我的控制器(MVC)吗?