Unit testing a WebAPI2 controller method with a header value
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.
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
};
}