45

I am using shanselmann's MvcMockHelper class to mock up some HttpContext stuff using Moq but the issue I am having is being able to assign something to my mocked session object in my MVC controller and then being able to read that same value in my unit test for verification purposes.

My question is how do you assign a storage collection to the mocked session object to allow code such as session["UserName"] = "foo" to retain the "foo" value and have it be available in the unit test.

FireStormHR
  • 37
  • 2
  • 9
rayray2030
  • 565
  • 1
  • 5
  • 11

7 Answers7

65

I started with Scott Hanselman's MVCMockHelper, added a small class and made the modifications shown below to allow the controller to use Session normally and the unit test to verify the values that were set by the controller.

/// <summary>
/// A Class to allow simulation of SessionObject
/// </summary>
public class MockHttpSession : HttpSessionStateBase
{
    Dictionary<string, object> m_SessionStorage = new Dictionary<string, object>();

    public override object this[string name]
    {
        get { return m_SessionStorage[name]; }
        set { m_SessionStorage[name] = value; }
    }
}

//In the MVCMockHelpers I modified the FakeHttpContext() method as shown below
public static HttpContextBase FakeHttpContext()
{
    var context = new Mock<HttpContextBase>();
    var request = new Mock<HttpRequestBase>();
    var response = new Mock<HttpResponseBase>();
    var session = new MockHttpSession();
    var server = new Mock<HttpServerUtilityBase>();

    context.Setup(ctx => ctx.Request).Returns(request.Object);
    context.Setup(ctx => ctx.Response).Returns(response.Object);
    context.Setup(ctx => ctx.Session).Returns(session);
    context.Setup(ctx => ctx.Server).Returns(server.Object);

    return context.Object;
}

//Now in the unit test i can do
AccountController acct = new AccountController();
acct.SetFakeControllerContext();
acct.SetBusinessObject(mockBO.Object);

RedirectResult results = (RedirectResult)acct.LogOn(userName, password, rememberMe, returnUrl);
Assert.AreEqual(returnUrl, results.Url);
Assert.AreEqual(userName, acct.Session["txtUserName"]);
Assert.IsNotNull(acct.Session["SessionGUID"]);

It's not perfect but it works enough for testing.

RonnBlack
  • 3,998
  • 6
  • 26
  • 28
  • 2
    Thanks for this sample, it's been very useful. I've modified your MockHttpSession slightly to return null rather than throw an exception when the key does not exist in the dictionary to more closely mimic the HttpSession object. Just a tip for other consumers. – DavidWhitney Nov 17 '09 at 11:02
  • I've been trying to do this w/o currently being able to reference a mocking framework, and your MockHttpSession is the best example I've found so far. I did discover that changing the getter as such get { return _sessionStorage.ContainsKey(name) ? _sessionStorage[name] : null; } will allow the testing of code which is written as -- if (sessionProperty["some key"] == null){} –  Apr 02 '12 at 18:19
40

Using Moq 3.0.308.2 here is an example of my account controller setup in my unit test:

    private AccountController GetAccountController ()
    {
      .. setup mocked services..

      var accountController = new AccountController (..mocked services..);

      var controllerContext = new Mock<ControllerContext> ();
      controllerContext.SetupGet(p => p.HttpContext.Session["test"]).Returns("Hello World");
      controllerContext.SetupGet(p => p.HttpContext.User.Identity.Name).Returns(_testEmail);
      controllerContext.SetupGet(p => p.HttpContext.Request.IsAuthenticated).Returns(true);
      controllerContext.SetupGet(p => p.HttpContext.Response.Cookies).Returns(new HttpCookieCollection ());

      controllerContext.Setup (p => p.HttpContext.Request.Form.Get ("ReturnUrl")).Returns ("sample-return-url");
      controllerContext.Setup (p => p.HttpContext.Request.Params.Get ("q")).Returns ("sample-search-term");

      accountController.ControllerContext = controllerContext.Object;

      return accountController;
    }

then within your controller method the following should return "Hello World"

string test = Session["test"].ToString ();
Todd Smith
  • 17,084
  • 11
  • 59
  • 78
3

I've made a slightly more elaborate Mock than the answer posted by @RonnBlack

public class HttpSessionStateDictionary : HttpSessionStateBase
{
    private readonly NameValueCollection keyCollection = new NameValueCollection();

    private readonly Dictionary<string, object> _values = new Dictionary<string, object>();

    public override object this[string name]
    {
        get { return _values.ContainsKey(name) ? _values[name] : null; }
        set { _values[name] = value; keyCollection[name] = null;}
    }

    public override int CodePage
    {
        get { throw new NotImplementedException(); }
        set { throw new NotImplementedException(); }
    }

    public override HttpSessionStateBase Contents
    {
        get { throw new NotImplementedException(); }
    }

    public override HttpCookieMode CookieMode
    {
        get { throw new NotImplementedException(); }
    }

    public override int Count
    {
        get { return _values.Count; }
    }

     public override NameObjectCollectionBase.KeysCollection Keys
{
    get { return keyCollection.Keys; }
}

    public Dictionary<string, object> UnderlyingStore
    {
        get { return _values; }
    }

    public override void Abandon()
    {
        _values.Clear();
    }

    public override void Add(string name, object value)
    {
        _values.Add(name, value);
    }

    public override void Clear()
    {
        _values.Clear();
    }

    public override void CopyTo(Array array, int index)
    {
        throw new NotImplementedException();
    }

    public override bool Equals(object obj)
    {
        return _values.Equals(obj);
    }

    public override IEnumerator GetEnumerator()
    {
        return _values.GetEnumerator();
    }

    public override int GetHashCode()
    {
        return (_values != null ? _values.GetHashCode() : 0);
    }

    public override void Remove(string name)
    {
        _values.Remove(name);
    }

    public override void RemoveAll()
    {
        _values.Clear();
    }

    public override void RemoveAt(int index)
    {
        throw new NotImplementedException();
    }

    public override string ToString()
    {
        return _values.ToString();
    }

    public bool Equals(HttpSessionStateDictionary other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return Equals(other._values, _values);
    }
}
SliverNinja - MSFT
  • 31,051
  • 11
  • 110
  • 173
Chris Marisic
  • 32,487
  • 24
  • 164
  • 258
  • updated to reflect **Keys** property fix - just need another collection for tracking keys - per [this related SO post](http://stackoverflow.com/a/13277399/175679) – SliverNinja - MSFT Jan 04 '16 at 11:59
2

I just found a nice example of how the Oxite team fakes their HttpSessionState and maintains a SessionStateItemCollection collection within that fake. This should work just as well as a moq in my case.

EDIT:

URL for this example is http://oxite.codeplex.com/sourcecontrol/changeset/view/33871?projectName=oxite#388065

Patrick McDonald
  • 64,141
  • 14
  • 108
  • 120
rayray2030
  • 565
  • 1
  • 5
  • 11
  • 1
    for the benefit of others finding this question through searches, could you please post a link to the information you found that answers the question. – Hamish Smith Mar 02 '09 at 23:08
  • I think he is talking about this class: http://oxite.codeplex.com/sourcecontrol/changeset/view/33871?projectName=oxite#388065 – andrecarlucci Jul 01 '09 at 14:30
0

Thank you, @RonnBlack for your solution! In my case, I kept getting this exception because Session.SessionID was null:

System.NotImplementedException was unhandled by user code
  HResult=-2147467263
  Message=The method or operation is not implemented.
  Source=System.Web
  StackTrace:
       at System.Web.HttpSessionStateBase.get_SessionID()

To solve this problem I implement @RonnBlack's code this way using the Moq Mock<HttpSessionStateBase> instead of his MockHttpSession:

    private readonly MyController controller = new MyController();

    [TestFixtureSetUp]
    public void Init()
    {
        var session = new Mock<HttpSessionStateBase>();
        session.Setup(s => s.SessionID).Returns(Guid.NewGuid().ToString());
        var request = new Mock<HttpRequestBase>();
        var response = new Mock<HttpResponseBase>();
        var server = new Mock<HttpServerUtilityBase>();
        // Not working - IsAjaxRequest() is static extension method and cannot be mocked
        // request.Setup(x => x.IsAjaxRequest()).Returns(true /* or false */);
        // use this
        request.SetupGet(x => x.Headers).Returns(
            new System.Net.WebHeaderCollection
            {
                {"X-Requested-With", "XMLHttpRequest"}
            });

        var context = new Mock<HttpContextBase>();
        //context
        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.SetupGet(x => x.Request).Returns(request.Object);
        context.SetupGet(p => p.Request.Url).Returns(new Uri("http://www.mytesturl.com"));
        var queryString = new NameValueCollection { { "code", "codeValue" } };
        context.SetupGet(r => r.Request.QueryString).Returns(queryString);

        controller.ControllerContext = new ControllerContext(context.Object, new RouteData(), controller);
    }

For details, please see http://weblogs.asp.net/gunnarpeipman/using-moq-to-mock-asp-net-mvc-httpcontextbase

user8128167
  • 6,929
  • 6
  • 66
  • 79
0

I think you can set an expectation on the mock with a specific value it should return whatever. Mocks are not used as actual fakes but rather things that you can assert behavior on.

It sounds like you are actually looking for an adapter that you can wrap around the session that you can supply a different implementation during tests and during runtime it would return HttpContext Session items?

Does this make sense?

Sean Chambers
  • 8,572
  • 7
  • 41
  • 55
0

Just for Session easier way is to create Session object in parent class and use it like this

    public class DalBl : IDalBl
{
    public dynamic Session
    {
        get { return HttpContext.Current.Session; }
    }
}

and in unitTest

            var session = new  Dictionary<string, object>();
        var moq = new Moq.Mock<IDalBl>();
        moq.Setup(d => d.Session).Returns(session);
Ali Humayun
  • 1,756
  • 1
  • 15
  • 12