Setting Httpcontext.Current.Session in a Unit Test

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.

How do I create an HttpContext for my unit test?

Unfortunately, it's pretty much impossible to test with HttpContext. It's a sealed class that doesn't utilize any interfaces, so you cannot mock it. Usually, your best bet is to abstract away the code that works with HttpContext, and then test just your other, more application-specific code.

It looks like you've sort of already done this via HttpContextAccessor, but you're utilizing it incorrectly. First, you're exposing the HttpContext instance, which pretty much defeats the entire purpose. This class should be able to return something like User.Identity.IsAuthenticated on its own, like: httpContextAccessor.IsAuthenticated. Internally, the property would access the private HttpContext instance and just return the result.

Once you're utilizing it in this way, you can then mock HttpContextAccessor to simply return what you need for your tests, and you don't have to worry about supplying it with an HttpContext instance.

Granted, this means there's still some untested code, namely, the accessor methods that work with HttpContext, but these are generally very straight-forward. For example, the code for IsAuthenticated would just be something like return httpContext.User.Identity.IsAuthenticated. The only way you're going to screw that up is if you fat-finger something, but the compiler will warn you about that.

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

How do I unit test HttpContext.Current.Request.LogonUserIdentity.Name ?

I spent a lot of time trying to mock HttpContext or HttpContextBase, then trying to shim with fakes WindowsIdentity class or HttpRequest.LogonUserIdentity property.
Nothing works - you don't need completely mocked HttpContext because you want see real responses, not your mock set ups returned.
Shims are just not generating for WindowsIdentity class ("due to internal limitations") and for LogonUserIdentity property (no reason given in fakes messages, it's just not there).

The best approach how to get testable HttpContext with request and response is described here:
http://jonlanceley.blogspot.com/2015/08/unit-testing-part-2-faking-httpcontext.html

I was able to override LogonUserIdentity property in my fake request wrapper and set there whatever I need.

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.



Related Topics



Leave a reply



Submit