Httpcontext.Current Is Null When Unit Test

HttpContext.Current is null when unit test

During unit tests HttpContext is always null as it is usually populate by IIS. You have a few options around this.

Sure, you could mock the HttpContext, (which you shouldn't really do - Don't mock HttpContext!!!! He doesn't like to be mocked!),. You should really try to stay away from tight coupling with HttpContext all over your code. Try constraining it to one central area (SRP);

Instead figure out what is the functionality you would like to achieve and design an abstraction around that. This will allow for your code to be more testable as it is not so tightly coupled to HttpContext.

Based on your example you are looking to access header values. This is just an example of how to change your thinking when it comes to using HttpContext.

Your original example has this

var request = HttpContext.Current.Request;
var testName = request.Headers.GetValues("OS Type")[0];

When you are looking for something like this

var testName = myService.GetOsType();

Well then create a service that provides that

public interface IHeaderService {
string GetOsType();
}

which could have a concrete implementation like

public class MyHeaderService : IHeaderService {

public string GetOsType() {
var request = HttpContext.Current.Request;
var testName = request.Headers.GetValues("OS Type")[0];
return testName;
}
}

Now in your controller you can have your abstraction instead of having tight coupling to HttpContext

public class MyApiController : ApiController {
IHeaderService myservice;
public MyApiController(IHeaderService headers) {
myservice = headers;
}

public IHttpActionResult Post([FromBody]TestDTO model) {
var testName = myService.GetOsType();
// more code

}
}

You can later inject your concrete type to get the functionality you want.

For testing you then swap dependencies to run your test.

If the method under test is your Post() method you can create a fake dependency or use a mocking framework

[TestClass]
public class MyTestClass {

public class MyFakeHeaderService : IHeaderService {
string os;
public MyFakeHeaderService(string os) {
this.os = os;
}

public string GetOsType() {
return os;
}
}

[TestMethod]
public void TestPostMethod() {
//Arrange
IHeaderService headers = new MyFakeHeaderService("FAKE OS TYPE");
var sut = new MyApiController(headers);
var model = new TestDTO();

//Act
sut.Post(model);

//Assert
//.....
}
}

HttpContext.Current.User is null, when it is access through unit test method

This code will create fake HttpContext:

    string UserName = "username";
HttpContext.Current = new HttpContext(
new HttpRequest("", "http://tempuri.org", ""),
new HttpResponse(new StringWriter()));
HttpContext.Current.User = new GenericPrincipal(
new GenericIdentity(UserName),new string[0]);

HttpContext.Current is null after await (only in unit tests )

HttpContext.Current is considered a pretty terrible property to work with; it doesn't behave itself outside of its ASP.NET home. The best way to fix your code is to stop looking at this property and find a way to isolate it from the code you are testing. For example, you could create an interface which represents your current session's data and expose that interface to the component you are testing, with an implementation that requires an HTTP context.


The root problem is to do with how the HttpContext.Current works. This property is "magical" in the ASP.NET framework, in that it is unique for a request-response operation, but jumps between threads as the execution requires it to - it is selectively shared between threads.

When you use HttpContext.Current outside of the ASP.NET processing pipeline, the magic goes away. When you switch threads like you are here with the asynchronous programming style, the property is null after continuing.

If you absolutely cannot change your code to remove the hard dependency on HttpContext.Current, you can cheat this test by leveraging your local context: all the variables in local scope when you declare a continuation are made available for the context of the continuation.

// Bring the current value into local scope.
var context = System.Web.HttpContext.Current;

var httpSessionStateBefore = context.Session;
var person = await Db.Persons.FirstOrDefaultAsync();
var httpSessionStateAfter = context.Session;

To be clear, this will only work for your current scenario. If you introduce an await ahead of this in another scope, the code will suddenly break again; this is the quick-and-dirty answer that I encourage you to ignore and pursue a more robust solution.

Setting HttpContext.Current.Session in a unit test

We had to mock HttpContext by using a HttpContextManager and calling the factory from within our application as well as the Unit Tests

public class HttpContextManager 
{
private static HttpContextBase m_context;
public static HttpContextBase Current
{
get
{
if (m_context != null)
return m_context;

if (HttpContext.Current == null)
throw new InvalidOperationException("HttpContext not available");

return new HttpContextWrapper(HttpContext.Current);
}
}

public static void SetCurrentContext(HttpContextBase context)
{
m_context = context;
}
}

You would then replace any calls to HttpContext.Current with HttpContextManager.Current and have access to the same methods. Then when you're testing, you can also access the HttpContextManager and mock your expectations

This is an example using Moq:

private HttpContextBase GetMockedHttpContext()
{
var context = new Mock<HttpContextBase>();
var request = new Mock<HttpRequestBase>();
var response = new Mock<HttpResponseBase>();
var session = new Mock<HttpSessionStateBase>();
var server = new Mock<HttpServerUtilityBase>();
var user = new Mock<IPrincipal>();
var identity = new Mock<IIdentity>();
var urlHelper = new Mock<UrlHelper>();

var routes = new RouteCollection();
MvcApplication.RegisterRoutes(routes);
var requestContext = new Mock<RequestContext>();
requestContext.Setup(x => x.HttpContext).Returns(context.Object);
context.Setup(ctx => ctx.Request).Returns(request.Object);
context.Setup(ctx => ctx.Response).Returns(response.Object);
context.Setup(ctx => ctx.Session).Returns(session.Object);
context.Setup(ctx => ctx.Server).Returns(server.Object);
context.Setup(ctx => ctx.User).Returns(user.Object);
user.Setup(ctx => ctx.Identity).Returns(identity.Object);
identity.Setup(id => id.IsAuthenticated).Returns(true);
identity.Setup(id => id.Name).Returns("test");
request.Setup(req => req.Url).Returns(new Uri("http://www.google.com"));
request.Setup(req => req.RequestContext).Returns(requestContext.Object);
requestContext.Setup(x => x.RouteData).Returns(new RouteData());
request.SetupGet(req => req.Headers).Returns(new NameValueCollection());

return context.Object;
}

and then to use it within your unit tests, I call this within my Test Init method

HttpContextManager.SetCurrentContext(GetMockedHttpContext());

you can then, in the above method add the expected results from Session that you're expecting to be available to your web service.

HttpContext is null in the controller during Unit Testing in Asp.net MVC

HttpContext.Current which you are faking in your test and accountController.HttpContext do not point to the same object. The answers you reference use HttpContext.Current in the controller code as well.

A better (more testable) approach might be to use ControllerContext.



Related Topics



Leave a reply



Submit