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
What Does a Lock Statement Do Under the Hood
Parallel.Foreach VS Task.Factory.Startnew
Automatically Create an Enum Based on Values in a Database Lookup Table
Wpf Binding UI Events to Commands in Viewmodel
Notification When a File Changes
Detecting Design Mode from a Control's Constructor
Call Asynchronous Method in Constructor
Entity Framework Join 3 Tables
List of Timezone Ids for Use with Findtimezonebyid() in C#
Differencebetween Task.Run() and Task.Factory.Startnew()
Entity Framework 4 - Addobject VS Attach
Generating an Xml Serialization Assembly as Part of My Build