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

I am writing unit test for an MVC 5 web application. I mocked HttpContext.Current from my test. When you run the following code form test httpSessionStateAfter throw

System.AggregateException: One or more errors occurred.
----> System.NullReferenceException: the reference to the object is not installed in the instance of the object.

This only happens when I run unit tests. When the application works, this work is beautiful. I am using Nunit 2.6.3 with a testable tester.

  var httpSessionStateBefour = System.Web.HttpContext.Current.Session; var Person= await Db.Persons.FirstOrDefaultAsync(); var httpSessionStateAfter = System.Web.HttpContext.Current.Session; 

How to deal with this problem?

This is how I make fun of 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; } } 
+5
source share
3 answers

HttpContext.Current is considered a pretty awful property to work with; he does not behave outside his 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 tested code. . For example, you can create an interface that represents your current session data and expose this interface to the component under test, with an implementation that requires an HTTP context.


The root problem is how HttpContext.Current works. This property is β€œmagic” in the ASP.NET structure because it is unique to the request-response operation, but transitions between threads require this as they are completed - it is selectively divided between threads.

When you use HttpContext.Current outside the ASP.NET processing pipeline, the magic disappears. When you switch threads, as you are here, with an 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 trick this test using your local context: all variables in the local scope, when you declare a continuation, the continuation becomes available for the context.

 // 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 type await before this in another area, the code will break again; this is a quick and dirty answer that I urge you to ignore and pursue a more reliable solution.

+8
source

First, I recommend that you isolate your code as much as possible from HttpContext.Current ; this will not only make your code more suitable for testing, but will help prepare you for ASP.NET vNext, which is more like OWIN (without HttpContext.Current ).

However, this may require many changes that you are not ready for. To make fun of HttpContext.Current , you need to understand how this works.

HttpContext.Current is a per-thread variable that is managed by ASP.NET SynchronizationContext . This SynchronizationContext is a "request context" representing the current request; it is created by ASP.NET when a new request arrives. I have an MSDN article on SynchronizationContext if you are 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 this to resume the async method. When the async method runs in the context of an ASP.NET request, the "context" captured by await is an ASP.NET SynchronizationContext . When the async method resumes (possibly in a different thread), ASP.NET SynchronizationContext set HttpContext.Current before resuming the async method. This is how async / await works on an ASP.NET host.

Now, when you run the same code in unit test, the behavior is different. In particular, there is no ASP.NET SynchronizationContext for setting HttpContext.Current . I assume your unit test method returns Task , in which case NUnit does not provide a SynchronizationContext at all. So, when the async method is resumed (possibly in a different thread), its HttpContext.Current may not be the same.

There are several different ways to fix this. One option is to write your own SynchronizationContext , which saves HttpContext.Current , like ASP.NET. A simpler (but less efficient) option is to use a SynchronizationContext , which I wrote under the name AsyncContext , which ensures that the async method resumes in the same thread, you should be able to install my AsyncEx library from NuGet , and then wrap your unit methods test during an AsyncContext.Run call. Note that unit test methods are now synchronous:

 [Test] public void MyTest() { AsyncContext.Run(async () => { // Test logic goes here: set HttpContext.Current, etc. }); } 
+9
source

I came to this question when I had a problem in my code ... where HTTPContext.Current is null after Await in Async MVC Action . I am posting it here because others like me can land here.

My general recommendation is to grab everything you want from the session into a local variable, like the others described above, but don't worry about keeping the context and instead just worry about grabbing the right elements.

 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 } 
+1
source

Source: https://habr.com/ru/post/1203704/


All Articles