I am writing unit test for MVC 5 web application. I have mocked the HttpContext.Current
from my test.
When run following code form the test httpSessionStateAfter
throw
System.AggregateException : One or more errors occurred.
----> System.NullReferenceException : Object reference not set to an instance of an object.
This happen only when i run the unit tests. When application run this work fine. I'm using Nunit 2.6.3 with reshaper test runner.
var httpSessionStateBefour = System.Web.HttpContext.Current.Session;
var Person= await Db.Persons.FirstOrDefaultAsync();
var httpSessionStateAfter = System.Web.HttpContext.Current.Session;
How to over come this problem?
This is how i mock the HttpContext
HttpContext.Current = Fakes.FakeHttpContext();
HttpContext.Current.Session.Add("IsUserSiteAdmin", true);
HttpContext.Current.Session.Add("CurrentSite", null);
public static class Fakes
{
public static HttpContext FakeHttpContext()
{
var httpRequest = new HttpRequest("", "http://stackoverflow/", "");
var stringWriter = new StringWriter();
var httpResponce = new HttpResponse(stringWriter);
var httpContext = new HttpContext(httpRequest, httpResponce);
var sessionContainer = new HttpSessionStateContainer("id", new SessionStateItemCollection(),
new HttpStaticObjectsCollection(), 10, true,
HttpCookieMode.AutoDetect,
SessionStateMode.InProc, false);
httpContext.Items["AspSession"] = typeof (HttpSessionState).GetConstructor(
BindingFlags.NonPublic | BindingFlags.Instance,
null, CallingConventions.Standard,
new[] {typeof (HttpSessionStateContainer)},
null)
.Invoke(new object[] {sessionContainer});
return httpContext;
}
}
First, I do recommend that you isolate your code as much as possible from HttpContext.Current
; not only will this make your code more testable, but it will help prepare you for ASP.NET vNext, which is more OWIN-like (without an HttpContext.Current
).
However, that can require a lot of changes, which you may not yet be ready for. To properly mock HttpContext.Current
, you need to understand how it works.
HttpContext.Current
is a per-thread variable that is controlled by the ASP.NET SynchronizationContext
. This SynchronizationContext
is a "request context", representing the current request; it's created by ASP.NET when a new request comes in. I have an MSDN article on SynchronizationContext
if you're interested in more details.
As I explain in my async
intro blog post, when you await
a Task
, by default it will capture the current "context" and use that to resume the async
method. When an async
method is running within an ASP.NET request context, the "context" captured by the await
is the ASP.NET SynchronizationContext
. When the async
method resumes (possibly on a different thread), the ASP.NET SynchronizationContext
will set HttpContext.Current
before resuming the async
method. This is how async
/await
works within an ASP.NET host.
Now, when you run the same code in a unit test, the behavior is different. Specifically, there is no ASP.NET SynchronizationContext
to set HttpContext.Current
. I'm assuming that your unit test method returns Task
, in which case NUnit does not provide a SynchronizationContext
at all. So, when the async
method resumes (possibly on a different thread), its HttpContext.Current
may not be the same one.
There's a few different ways to fix this. One option is to write your own SynchronizationContext
that preserves HttpContext.Current
, just like the ASP.NET one does. An easier (but less efficient) option is to use a SynchronizationContext
that I wrote called AsyncContext
, which ensures the async
method will resume on the same thread. You should be able to install my AsyncEx
library from NuGet and then wrap your unit test methods inside a call to AsyncContext.Run
. Note that the unit test methods are now synchronous:
[Test]
public void MyTest()
{
AsyncContext.Run(async () =>
{
// Test logic goes here: set HttpContext.Current, etc.
});
}
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.
I came to this question when having an issue in my code... where the HTTPContext.Current is null after an Await in an Async MVC Action. I post this here because others like me might land here.
My general recommendation is to grab anything you want off the session into a local variable, much like others above discuss, but not worrying about keeping the context, and instead just worrying about grabbing the actual items you want.
public async Task<ActionResult> SomeAction(SomeModel model)
{
int id = (int)HttpContext.Current.Session["Id"];
/* Session Exists Here */
var somethingElseAsyncModel = await GetSomethingElseAsync(model);
/* Session is Null Here */
// Do something with id, thanks to the fact we got it when we could
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With