Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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;
    }
}
like image 291
Milina Udara Avatar asked Sep 30 '14 10:09

Milina Udara


3 Answers

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.
  });
}
like image 134
Stephen Cleary Avatar answered Nov 09 '22 06:11

Stephen Cleary


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.

like image 42
Paul Turner Avatar answered Nov 09 '22 06:11

Paul Turner


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
}
like image 39
Greg Avatar answered Nov 09 '22 04:11

Greg