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
How to Know What Image Format I Get from a Stream
Using C# to Search a CSV File and Pull the Value in the Column Next to It
How to Fix the Error:"Unreachable Code Detected"
How to Delete a Row from Gridview
Combining Datatemplates at Runtime
Using C# to Authenticate User Against Ldap
Syncing SQL Server 2008 Databases Over Http Using Wcf & Sync Framework
Execute Multiple Queries in Single Oracle Command in C#
Dynamically Switch Wcf Web Service Reference Url Path Through Config File
How to Validate Signature of Jwt from Jwks Without X5C
Getting Correct Image Rotation
Best Way to Switch Behavior Based on Type
JSON Serializer Object with Internal Properties
How to Capitalize First Letter of First Name and Last Name in C#
How to Parse Dates with a Suffix "Th", "St" or "Nd" on the Day of the Month