Mock HttpContext for unit testing a .NET core MVC controller?

C#asp.net MvcUnit Testingasp.net Core

C# Problem Overview


I have a function in a controller that I am unit testing that expects values in the header of the http request. I can't initialize the HttpContext because it is readonly.

My controller function expects a http request header value for "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);
}

Is there a straight-forward way to do this without using a third party library?

C# Solutions


Solution 1 - C#

I was able to initialize the httpcontext and header in this way:

[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
    ...
}

Solution 2 - C#

Rather than mocking out the HTTPContext, it is probably a better idea to map the header into a parameter on the method. For example, in the controller at the bottom of this answer, the id parameter is set to the value header with a name equal to "device-id"... The unit test then becomes

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

While you can mock the HttpContext, in my opinion it is something that should be avoided unless you have no choice. The documentation for the FromHeaderAttribute can be found here [FromHeaderAttribute Class][1].

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

[1]: https://docs.microsoft.com/en-us/aspnet/core/api/microsoft.aspnetcore.mvc.fromheaderattribute#Microsoft_AspNetCore_Mvc_FromHeaderAttribute "FromHeaderAttribute Class"

Solution 3 - C#

For people in need of a header but also additional data in their HttpContext, you can do so by initializing the context with features thanks to the second constructor of the DefaultHttpContext class:

1. Create a header dictionary with the headers you need:
var headers = new Dictionary<string, StringValues>
{
   { "myHeaderKey", "myHeaderValue" },
};
var headerDictionary = new HeaderDictionary(headers)
2. Create an HttpRequestFeature with the previously created header dictionary:
var requestFeature = new HttpRequestFeature()
{
    Headers = headerDictionary,
};
3. Create a Feature collection containing the feature previously created :
 var features = new FeatureCollection();

features.Set<IHttpRequestFeature>(requestFeature);
4. Initialize the DefaultHttpContext with the feature collection, and set it as the HttpContext of your controller:
var httpContext = new DefaultHttpContext(features);

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

The controller's context will have the correct headers set, and you can still feed the context with more data as needed by setting additional HttpContext attributes to the featureCollection before instantiating the DefaultHttpContext (like feature.Set<IQueryFeature>(new QueryFeature(...)) for the query string for instance).

PS: For a more in-depth explanation on using features to mock (and unit testing in general) an HttpContext, see: https://weblogs.asp.net/ricardoperes/unit-testing-the-httpcontext-in-controllers

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJames WierzbaView Question on Stackoverflow
Solution 1 - C#James WierzbaView Answer on Stackoverflow
Solution 2 - C#GlennSillsView Answer on Stackoverflow
Solution 3 - C#adamencyView Answer on Stackoverflow