195

I have a web service I am trying to unit test. In the service it pulls several values from the HttpContext like so:

 m_password = (string)HttpContext.Current.Session["CustomerId"];
 m_userID = (string)HttpContext.Current.Session["CustomerUrl"];

in the unit test I am creating the context using a simple worker request, like so:

SimpleWorkerRequest request = new SimpleWorkerRequest("", "", "", null, new StringWriter());
HttpContext context = new HttpContext(request);
HttpContext.Current = context;

However, whenever I try to set the values of HttpContext.Current.Session

HttpContext.Current.Session["CustomerId"] = "customer1";
HttpContext.Current.Session["CustomerUrl"] = "customer1Url";

I get null reference exception that says HttpContext.Current.Session is null.

Is there any way to initialize the current session within the unit test?

CodeNotFound
  • 18,731
  • 6
  • 54
  • 63
DaveB
  • 2,029
  • 2
  • 13
  • 11

14 Answers14

313

You can "fake it" by creating a new HttpContext like this:

http://www.necronet.org/archive/2010/07/28/unit-testing-code-that-uses-httpcontext-current-session.aspx

I've taken that code and put it on an static helper class like so:

public static HttpContext FakeHttpContext()
{
    var httpRequest = new HttpRequest("", "http://example.com/", "");
    var stringWriter = new StringWriter();
    var httpResponse = new HttpResponse(stringWriter);
    var httpContext = new HttpContext(httpRequest, httpResponse);

    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;
}

Or instead of using reflection to construct the new HttpSessionState instance, you can just attach your HttpSessionStateContainer to the HttpContext (as per Brent M. Spell's comment):

SessionStateUtility.AddHttpSessionStateToContext(httpContext, sessionContainer);

and then you can call it in your unit tests like:

HttpContext.Current = MockHelper.FakeHttpContext();
Community
  • 1
  • 1
Milox
  • 5,514
  • 5
  • 26
  • 36
  • 28
    I like this answer better than the accepted one because changing your production code to support your testing activities is bad practice. Granted, your production code should abstract out 3rd party namespaces like this, but when you're working with legacy code you don't always have this control or the luxury to re-factor. – Sean Glover Aug 23 '12 at 13:30
  • 30
    You don't have to use reflection to construct the new HttpSessionState instance. You can just attach your HttpSessionStateContainer to the HttpContext using SessionStateUtility.AddHttpSessionStateToContext. – Brent M. Spell Sep 26 '12 at 15:29
  • MockHelper is just the name of the class where the static method is, you can use whatever name you prefer. – Milox Aug 16 '13 at 21:51
  • I've tried implementing your answer but the Session is still null. Would you please take a look at my Post http://stackoverflow.com/questions/23586765/mocking-session-not-working-in-mvc-5. Thank you – Joe May 10 '14 at 21:58
  • `Server.MapPath()` won't work if you use this either. – Yuck Dec 22 '14 at 17:39
  • Any ideas how to write to `HttpRequest.InputStream` using this approach? – Steven de Salas Mar 12 '15 at 05:35
  • This answer worked after the accepted answer didn't. Thank you :) – Omar.Ebrahim Jan 28 '16 at 10:10
  • I'm getting an empty `HttpContext.Current.Request.ApplicationPath`, which may be why `Server.MapPath()` doesn't work @Yuck? Any idea how to set it? – drzaus Jul 14 '16 at 18:17
  • I tried to use the `FakeHttpContext`, but I had to abandon it, because it cannot set the request headers. So I had to use the `HttpContextManager` instead. – John Henckel Aug 03 '16 at 18:36
  • this is pretty old, but i use in some of my code UrlHelper uHelper = new UrlHelper(_context.Request.RequestContext); , and then i urn uHelper.Content(...) on some urls. This gives me a null exception when i use your example for tests. How can i edit this so that the urlhelper made from this context works? – Phil Apr 04 '19 at 20:39
110

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.

CodeNotFound
  • 18,731
  • 6
  • 54
  • 63
Anthony Shaw
  • 8,043
  • 4
  • 39
  • 61
  • 1
    but this doesn't use SimpleWorkerRequest – knocte Aug 27 '12 at 20:06
  • he was trying to mock out the HttpContext so that his SimpleWorkerRequest would have access to the values from the HttpContext, he would use the HttpContextFactory within his service – Anthony Shaw Aug 28 '12 at 17:51
  • Is it intentional that the backing field m_context is only returned for a mock context (when set via SetCurrentContext) and that for the real HttpContext, a wrapper is created for every call to Current? – Stephen Price Jul 01 '13 at 08:44
  • Yes it is. m_context is of type HttpContextBase and returning HttpContextWrapper returns HttpContextBase with the Current HttpContext – Anthony Shaw Jul 01 '13 at 13:42
  • I think the class should not be called a factory but something else, maybe `HttpContextSource` because it does not create new objects. – user1713059 May 08 '15 at 15:12
  • 1
    `HttpContextManager` would be a better name than `HttpContextSource` but I agree `HttpContextFactory` is misleading. – Professor of programming Sep 18 '15 at 10:01
  • @AnthonyShaw: I have implemented your code, But still It is giving `HttpContext.current` is null in Mvc Action. – Amit Kumar Oct 21 '15 at 04:33
  • @AnthonyShaw : what is 'Mvcapplication' in this line of code 'MvcApplication.RegisterRoutes(routes);' in 'GetMockedHttpContext' method. If its a built-in type, can you please let me know its namespace. Thanks. – ganesh Jul 15 '19 at 03:59
48

Milox solution is better than the accepted one IMHO but I had some problems with this implementation when handling urls with querystring.

I made some changes to make it work properly with any urls and to avoid Reflection.

public static HttpContext FakeHttpContext(string url)
{
    var uri = new Uri(url);
    var httpRequest = new HttpRequest(string.Empty, uri.ToString(),
                                        uri.Query.TrimStart('?'));
    var stringWriter = new StringWriter();
    var httpResponse = new HttpResponse(stringWriter);
    var httpContext = new HttpContext(httpRequest, httpResponse);

    var sessionContainer = new HttpSessionStateContainer("id",
                                    new SessionStateItemCollection(),
                                    new HttpStaticObjectsCollection(),
                                    10, true, HttpCookieMode.AutoDetect,
                                    SessionStateMode.InProc, false);

    SessionStateUtility.AddHttpSessionStateToContext(
                                         httpContext, sessionContainer);

    return httpContext;
}
Community
  • 1
  • 1
giammin
  • 17,130
  • 6
  • 64
  • 84
  • This allows you to fake `httpContext.Session`, any idea how to do the same for `httpContext.Application`? – KyleMit Aug 19 '16 at 13:28
41

I worte something about this a while ago.

Unit Testing HttpContext.Current.Session in MVC3 .NET

Hope it helps.

[TestInitialize]
public void TestSetup()
{
    // We need to setup the Current HTTP Context as follows:            

    // Step 1: Setup the HTTP Request
    var httpRequest = new HttpRequest("", "http://localhost/", "");

    // Step 2: Setup the HTTP Response
    var httpResponce = new HttpResponse(new StringWriter());

    // Step 3: Setup the Http Context
    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 });

    // Step 4: Assign the Context
    HttpContext.Current = httpContext;
}

[TestMethod]
public void BasicTest_Push_Item_Into_Session()
{
    // Arrange
    var itemValue = "RandomItemValue";
    var itemKey = "RandomItemKey";

    // Act
    HttpContext.Current.Session.Add(itemKey, itemValue);

    // Assert
    Assert.AreEqual(HttpContext.Current.Session[itemKey], itemValue);
}
Ro Hit
  • 1,199
  • 10
  • 10
12

If you're using the MVC framework, this should work. I used Milox's FakeHttpContext and added a few additional lines of code. The idea came from this post:

http://codepaste.net/p269t8

This seems to work in MVC 5. I haven't tried this in earlier versions of MVC.

HttpContext.Current = MockHttpContext.FakeHttpContext();

var wrapper = new HttpContextWrapper(HttpContext.Current);

MyController controller = new MyController();
controller.ControllerContext = new ControllerContext(wrapper, new RouteData(), controller);

string result = controller.MyMethod();
Community
  • 1
  • 1
Nimblejoe
  • 1,179
  • 2
  • 11
  • 15
12

You can try FakeHttpContext:

using (new FakeHttpContext())
{
   HttpContext.Current.Session["CustomerId"] = "customer1";       
}
vAD
  • 231
  • 3
  • 6
8

In asp.net Core / MVC 6 rc2 you can set the HttpContext

var SomeController controller = new SomeController();

controller.ControllerContext = new ControllerContext();
controller.ControllerContext.HttpContext = new DefaultHttpContext();
controller.HttpContext.Session = new DummySession();

rc 1 was

var SomeController controller = new SomeController();

controller.ActionContext = new ActionContext();
controller.ActionContext.HttpContext = new DefaultHttpContext();
controller.HttpContext.Session = new DummySession();

https://stackoverflow.com/a/34022964/516748

Consider using Moq

new Mock<ISession>();
Community
  • 1
  • 1
KCD
  • 8,614
  • 3
  • 57
  • 66
7

The answer that worked with me is what @Anthony had written, but you have to add another line which is

    request.SetupGet(req => req.Headers).Returns(new NameValueCollection());

so you can use this:

HttpContextFactory.Current.Request.Headers.Add(key, value);
yzicus
  • 161
  • 1
  • 4
2

Try this:

        // MockHttpSession Setup
        var session = new MockHttpSession();

        // MockHttpRequest Setup - mock AJAX request
        var httpRequest = new Mock<HttpRequestBase>();

        // Setup this part of the HTTP request for AJAX calls
        httpRequest.Setup(req => req["X-Requested-With"]).Returns("XMLHttpRequest");

        // MockHttpContextBase Setup - mock request, cache, and session
        var httpContext = new Mock<HttpContextBase>();
        httpContext.Setup(ctx => ctx.Request).Returns(httpRequest.Object);
        httpContext.Setup(ctx => ctx.Cache).Returns(HttpRuntime.Cache);
        httpContext.Setup(ctx => ctx.Session).Returns(session);

        // MockHttpContext for cache
        var contextRequest = new HttpRequest("", "http://localhost/", "");
        var contextResponse = new HttpResponse(new StringWriter());
        HttpContext.Current = new HttpContext(contextRequest, contextResponse);

        // MockControllerContext Setup
        var context = new Mock<ControllerContext>();
        context.Setup(ctx => ctx.HttpContext).Returns(httpContext.Object);

        //TODO: Create new controller here
        //      Set controller's ControllerContext to context.Object

And Add the class:

public class MockHttpSession : HttpSessionStateBase
{
    Dictionary<string, object> _sessionDictionary = new Dictionary<string, object>();
    public override object this[string name]
    {
        get
        {
            return _sessionDictionary.ContainsKey(name) ? _sessionDictionary[name] : null;
        }
        set
        {
            _sessionDictionary[name] = value;
        }
    }

    public override void Abandon()
    {
        var keys = new List<string>();

        foreach (var kvp in _sessionDictionary)
        {
            keys.Add(kvp.Key);
        }

        foreach (var key in keys)
        {
            _sessionDictionary.Remove(key);
        }
    }

    public override void Clear()
    {
        var keys = new List<string>();

        foreach (var kvp in _sessionDictionary)
        {
            keys.Add(kvp.Key);
        }

        foreach(var key in keys)
        {
            _sessionDictionary.Remove(key);
        }
    }
}

This will allow you to test with both session and cache.

1

I was looking for something a little less invasive than the options mentioned above. In the end I came up with a cheesy solution, but it might get some folks moving a little faster.

First I created a TestSession class:

class TestSession : ISession
{

    public TestSession()
    {
        Values = new Dictionary<string, byte[]>();
    }

    public string Id
    {
        get
        {
            return "session_id";
        }
    }

    public bool IsAvailable
    {
        get
        {
            return true;
        }
    }

    public IEnumerable<string> Keys
    {
        get { return Values.Keys; }
    }

    public Dictionary<string, byte[]> Values { get; set; }

    public void Clear()
    {
        Values.Clear();
    }

    public Task CommitAsync()
    {
        throw new NotImplementedException();
    }

    public Task LoadAsync()
    {
        throw new NotImplementedException();
    }

    public void Remove(string key)
    {
        Values.Remove(key);
    }

    public void Set(string key, byte[] value)
    {
        if (Values.ContainsKey(key))
        {
            Remove(key);
        }
        Values.Add(key, value);
    }

    public bool TryGetValue(string key, out byte[] value)
    {
        if (Values.ContainsKey(key))
        {
            value = Values[key];
            return true;
        }
        value = new byte[0];
        return false;
    }
}

Then I added an optional parameter to my controller's constructor. If the parameter is present, use it for session manipulation. Otherwise, use the HttpContext.Session:

class MyController
{

    private readonly ISession _session;

    public MyController(ISession session = null)
    {
        _session = session;
    }


    public IActionResult Action1()
    {
        Session().SetString("Key", "Value");
        View();
    }

    public IActionResult Action2()
    {
        ViewBag.Key = Session().GetString("Key");
        View();
    }

    private ISession Session()
    {
        return _session ?? HttpContext.Session;
    }
}

Now I can inject my TestSession into the controller:

class MyControllerTest
{

    private readonly MyController _controller;

    public MyControllerTest()
    {
        var testSession = new TestSession();
        var _controller = new MyController(testSession);
    }
}
Chris Hanson
  • 1,843
  • 3
  • 14
  • 25
1

Never mock.. never! The solution is pretty simple. Why fake such a beautiful creation like HttpContext?

Push the session down! (Just this line is enough for most of us to understand but explained in detail below)

(string)HttpContext.Current.Session["CustomerId"]; is how we access it now. Change this to

_customObject.SessionProperty("CustomerId")

When called from test, _customObject uses alternative store (DB or cloud key value[ http://www.kvstore.io/] )

But when called from the real application, _customObject uses Session.

how is this done? well... Dependency Injection!

So test can set the session(underground) and then call the application method as if it knows nothing about the session. Then test secretly checks if the application code correctly updated the session. Or if the application behaves based on the session value set by the test.

Actually, we did end up mocking even though I said: "never mock". Becuase we couldn't help but slip to the next rule, "mock where it hurts the least!". Mocking huge HttpContext or mocking a tiny session, which hurts the least? don't ask me where these rules came from. Let us just say common sense. Here is an interesting read on not mocking as unit test can kills us

Blue Clouds
  • 4,483
  • 2
  • 30
  • 53
0

The answer @Ro Hit gave helped me a lot, but I was missing the user credentials because I had to fake a user for authentication unit testing. Hence, let me describe how I solved it.

According to this, if you add the method

    // using System.Security.Principal;
    GenericPrincipal FakeUser(string userName)
    {
        var fakeIdentity = new GenericIdentity(userName);
        var principal = new GenericPrincipal(fakeIdentity, null);
        return principal;
    }

and then append

    HttpContext.Current.User = FakeUser("myDomain\\myUser");

to the last line of the TestSetup method you're done, the user credentials are added and ready to be used for authentication testing.

I also noticed that there are other parts in HttpContext you might require, such as the .MapPath() method. There is a FakeHttpContext available, which is described here and can be installed via NuGet.

Community
  • 1
  • 1
Matt
  • 21,449
  • 14
  • 100
  • 149
0

I found the following simple solution for specifying a user in the HttpContext: https://forums.asp.net/post/5828182.aspx

0

Try this way..

public static HttpContext getCurrentSession()
  {
        HttpContext.Current = new HttpContext(new HttpRequest("", ConfigurationManager.AppSettings["UnitTestSessionURL"], ""), new HttpResponse(new System.IO.StringWriter()));
        System.Web.SessionState.SessionStateUtility.AddHttpSessionStateToContext(
        HttpContext.Current, new HttpSessionStateContainer("", new SessionStateItemCollection(), new HttpStaticObjectsCollection(), 20000, true,
        HttpCookieMode.UseCookies, SessionStateMode.InProc, false));
        return HttpContext.Current;
  }
Android
  • 1,358
  • 4
  • 10
  • 22