1

I am trying to write some unit tests for fairly basic controller actions, in which I set Session["User"]. When I run the test, it is failing at this point with a null reference exception. Not entirely sure how to get round this.

Controller:

public ActionResult Index(int id)
        {
            Session["User"] = Id;

            if (Id != 0)
            {
                return RedirectToAction("Home", "Home", new { Id});
            }

            return RedirectToAction("Register", "Home", new { Id});
        }

Test:

[TestMethod]
        public void NewUser_ShouldGoToRegister()
        {
            var controller = new HomeController();

            HttpContext.Current = FakeHttpContext();

            HttpContext.Current.Session.Add("User", "0");

            var result = controller.Index(0) as ViewResult;

            Assert.AreEqual("Register", result.ViewName);
        }


        public HttpContext FakeHttpContext()
        {
            var httpRequest = new HttpRequest("", "http://localhost/", "");

            var httpResponce = new HttpResponse(new 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 });


            HttpContext.Current = httpContext;
        }
ayo1234
  • 269
  • 3
  • 10

2 Answers2

1

You can use some mocking tool like MOQ for mocking, to create fake HttpContext you can try like following code.

[TestMethod]
public void NewUser_ShouldGoToRegister()
{
    var controller = new HomeController();
    HttpContext.Current = FakeHttpContext();
    HttpContext.Current.Session.Add("User", 1);
    var result = controller.Index(0) as ViewResult;
    Assert.AreEqual("Register", result.ViewName);
}

Your FakeHttpContext method should look like following code.

        //Creating a Fake HTTP Context           
        // This is used for testing methods using Session Variables.
        private HttpContext FakeHttpContext()
        {
            var httpRequest = new HttpRequest("", "http://somethig.com/", "");
            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;
        }
PSK
  • 15,515
  • 4
  • 25
  • 38
  • I've just tried using this method and I still get a null session when i debug into it. Any ideas? – ayo1234 Apr 30 '18 at 09:01
  • Still null. It's definitely set's up the session in the test but when it goes through the the controller it's null – ayo1234 Apr 30 '18 at 09:07
  • did you checked https://stackoverflow.com/questions/9624242/setting-httpcontext-current-session-in-a-unit-test – PSK Apr 30 '18 at 09:10
  • I have looked at that question but no success :/ – ayo1234 Apr 30 '18 at 09:15
  • this is strange, it always worked for me. Can you check the session key, you are trying to access the same which you are adding? – PSK Apr 30 '18 at 09:16
  • Sorry if i sound dumb but what do you mean? – ayo1234 Apr 30 '18 at 09:18
  • I mean, what you are setting in session using a key, are you using same key while getting it from session. – PSK Apr 30 '18 at 09:20
  • Yes that's all the same i think – ayo1234 Apr 30 '18 at 09:23
  • I have no idea what is going on. I've checked everything and it all looks correct but once I go into that controller, suddent HttpContect.Current doesn't exist but HttpContext.Session does and it's null. I'm so confused. – ayo1234 Apr 30 '18 at 10:14
0

If you have difficulties with this, this idea might help you overthinking it. Sometimes it's a good idea to create another layer of access to Session.

A simplified way would be to never directly access "Session", but rather have it wrapped into methods like this:

protected virtual string ReadFromSession(string key)
{
    return Session[key];
}

protected virtual void StoreInSession(string key, string value)
{
    Session[key] = value;
}

Everywhere in your Controller, you just use these methods instead of directly accessing Session. Easy enough.

Now the trick is that with Unit Test, you can override the Controller which you test:

public class MyControllerForTesting : MyController
{
    private readonly IDictionary session;

    public MyControllerForTesting(IDictionary session) : base()
    {
        this.session = session;
    }

    protected override string ReadFromSession(string key)
    {
        return this.session[key];
    }

    protected override void StoreInSession(string key, string value)
    {
        this.session[key] = value;
    }
}

Now this might not work immediately as I didn't really test it but you get the Idea. Your unit tests work and you even can inspect the Dictionary if the values were set correctly etc.

thmshd
  • 5,473
  • 2
  • 34
  • 61