Question

I'd like to "unit" test a method on my WebAPI contoller.

This method relies on a header being sent with it.

So

HttpContext.Current.Request.Headers["name"]

needs to have a value in the method body.

What is the best way of doing this? I thought I'd be able to set the ControllerContext which would populate HttpContext, but can't get it working.

I'd prefer not to use a mocking framework or any other third party tools, as my understanding is that WebAPI2 plays nicely with this use case.

I'm happy to set HttpContext.Current if that's the best way.

Was it helpful?

Solution 2

Sometimes, you have little/no control of the code you are writing tests for. If it's already been designed to use HttpContext.Current, and you keep getting "Operation is not supported on this platform." errors like i struggled with, this will help.

public static class NameValueCollectionExtensions
{
    public static NameValueCollection AddValue(this NameValueCollection headers, string key, string value)
    {
        Type t = headers.GetType();
        t.InvokeMember("MakeReadWrite", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, headers, null);
        t.InvokeMember("InvalidateCachedArrays", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, headers, null);
        t.InvokeMember("BaseAdd", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, headers, new object[] { key, new System.Collections.ArrayList() { value } });
        t.InvokeMember("MakeReadOnly", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, headers, null);
        return headers;
    }
}

With that class in the same namespace, you can add the headers like:

HttpContext.Current.Request.Headers.AddValue("header_key", "header_value");

Of course, if you don't like extension methods, you could always use a wrapper method instead.

I hope this helps someone.

OTHER TIPS

Hi I might be a little late to the party but I ran into the same problem and I here is what I ended up doing.

As others have noted, use Request.Headers instead of HttpCurrentContext in your controller actions e.g.

    [Route("")]
    [HttpGet]
    public IHttpActionResult Get()
    {
        // The header can have multiple values, I call SingleOrDefault as I only expect 1 value.
        var myHeader = Request.Headers.GetValues("X-My-Header").SingleOrDefault();
        if (myHeader == "success")
        {
             return Ok<string>("Success!");
        }

         return BadRequest();
    }

It is then really easy to create a HttpControllerContext and set the request property like this:

[TestMethod]
public void Get_HeaderIsValid()
{
    // Arrange
    var controller = new ConfigurationsController(null);
    var controllerContext = new HttpControllerContext();
    var request = new HttpRequestMessage();
    request.Headers.Add("X-My-Header", "success");

    // Don't forget these lines, if you do then the request will be null.
    controllerContext.Request = request;
    controller.ControllerContext = controllerContext;

    // Act
    var result = controller.Get() as OkNegotiatedContentResult<string>;

    // Assert
    Assert.IsNotNull(result);
    Assert.AreEqual("Success!", result.Content);
}

Hope this helps :)

P.s. Don't forget to add Web.Api.Core Reference to the test project :)

Note: This answer works for the generic title of the question, however in this particular case the user has external code that relies on HttpContext.Current that is outside his control. If this is your case as well this is not the way to go. For most other users this is still recommended

Don't rely on HttpContext.Current in WebAPI. It's recommended in general to avoid using it in WebAPI, one of the main reasons is unit testability.

Also note I'm returning an IHttpActionResult that will make testing even easier.

Instead just use the controller member Request.Headers and then you can set it through the context object in your test

public class MyController : ApiController
{
    public IHttpActionResult Get()
    {
         if (Request.Headers. /* insert your code here */)
         {
             // Do Something
         }
    }
 }

 public class TestClass
 {
     public void Test()
     {
         // Arrange
         var controller = new MyController();
         var request = new HttpRequestMessage();
         request.Headers... // setup your test here

         // Act
         var result = controller.Get();

         // Assert
         // Verify here
     }
 }

Here is an example for a full end-end in memory integration test (again note that you need to use the Request property that is available throughout the pipeline rather than HttpContext.Current. This code was taken from: WebAPI tests there a few more styles of integration tests in the code.

// Do any setup work
HttpConfiguration config = new HttpConfiguration();
config.Routes.MapHttpRoute("Default", "{controller}/{action}");

// Setup in memory server and client
HttpServer server = new HttpServer(config);
HttpClient client = new HttpClient(server);

// Act
HttpResponseMessage response = client.GetAsync("http://localhost/" + requestUrl).Result;

// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(count, response.Content.ReadAsAsync<int>().Result);

I would suggest it.
While you create your private controller objects set these settings at that time.

    private HomeController createHomeController()
    {
        var httpContext = new DefaultHttpContext();
        httpContext.Request.Headers["Key"] = "value123";
        var controllerContext = new ControllerContext
        {
            HttpContext = httpContext
        };
          
        return new HomeController()
        {
            ControllerContext = controllerContext
        };
    }

You can mock the HTTP request context. Are you using a mocking framework like Moq? That makes it easy to mock the request headers collection. If not using Moq, see this SO question.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top