为了对 .NET core MVC 控制器进行单元测试,如何模拟 HttpContext?

140

我在控制器中有一个函数需要进行单元测试,它期望在HTTP请求的头部传递值。由于HttpContext是只读的,我无法初始化它。

我的控制器函数需要HTTP请求头中的“device-id”值。

[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);
}

是否有一种直接的方法可以在不使用第三方库的情况下完成这个任务?


2
不要使用HttpContext?使用控制器的整个目的是数据通过控制器的参数传递。如果您的控制器使用HttpContext读取数据,就像它是WebForms页面一样,那么就有问题了。 - Panagiotis Kanavos
@PanagiotisKanavos 头部中的值是一段信息,指示呼叫来自哪个移动设备。这是检索正确数据所必需的。设备ID在头部中,因为需要该ID进行身份验证,这由自定义操作过滤器处理。我可以将设备ID作为路由参数传递,但这将是冗余的。 - James Wierzba
检查 FromHeaderAttribute 但也检查重复。HttpContext 现在可以通过配置进行注入。 - Panagiotis Kanavos
1
我建议你修改你的问题,明确你想要什么(访问头部字段以识别移动设备)。ASP.NET文档似乎正在经历一个...“过渡”时期,文档页面有所缺失。查看此几乎相同的问题,它询问如何根据HTTP头部值路由移动设备:这里的链接 - Panagiotis Kanavos
3个回答

312

我能够用这种方式初始化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
    ...
}

1
简单完美的解决方案。 - Jaime Bula

22

与其对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;
    }
}

1
在我的情况下,如果我没记错的话,需要将它包含在HTTP头中,因为相同的值需要在.NET Core中间件组件中进行评估。 - James Wierzba

6

如果需要在HttpContext中添加标题和其他数据,可以通过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之前将其他HttpContext属性设置到featureCollection中来提供更多数据给上下文(例如对于查询字符串,使用feature.Set<IQueryFeature>(new QueryFeature(...)))。

附注:有关使用功能模拟(以及普遍情况下的单元测试)HttpContext的更深入解释,请参见:https://weblogs.asp.net/ricardoperes/unit-testing-the-httpcontext-in-controllers


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接