[asp.net-mvc] ASP/NET MVC: Test Controllers w/Sessions? Mocking?


2 Answers

The ASP.NET MVC framework is not very mock-friendly (or rather, requires too much setup to mock properly, and causes too much friction when testing, IMHO) due to it's use of abstract base classes instead of interfaces. We've had good luck writing abstractions for per-request and session-based storage. We keep those abstractions very light and then our controllers depend upon those abstractions for per-request or per-session storage.

For example, here's how we manage the forms auth stuff. We have an ISecurityContext:

public interface ISecurityContext
{
    bool IsAuthenticated { get; }
    IIdentity CurrentIdentity { get; }
    IPrincipal CurrentUser { get; set; }
}

With a concrete implementation like:

public class SecurityContext : ISecurityContext
{
    private readonly HttpContext _context;

    public SecurityContext()
    {
        _context = HttpContext.Current;
    }

    public bool IsAuthenticated
    {
        get { return _context.Request.IsAuthenticated; }
    }

    public IIdentity CurrentIdentity
    {
        get { return _context.User.Identity; }
    }

    public IPrincipal CurrentUser
    {
        get { return _context.User; }
        set { _context.User = value; }
    }
}
Question

I read some of the answers on here re: testing views and controllers, and mocking, but I still can't figure out how to test an ASP.NET MVC controller that reads and sets Session values (or any other context based variables.) How do I provide a (Session) context for my test methods? Is mocking the answer? Anybody have examples? Basically, I'd like to fake a session before I call the controller method and have the controller use that session. Any ideas?




I used the following solution - making a controller that all my other controllers inherit from.

public class TestableController : Controller
{

    public new HttpSessionStateBase Session
    {
        get
        {
            if (session == null)
            {
                session = base.Session ?? new CustomSession();
            }
            return session;
        }
    }
    private HttpSessionStateBase session;

    public class CustomSession : HttpSessionStateBase
    {

        private readonly Dictionary<string, object> dictionary; 

        public CustomSession()
        {
            dictionary = new Dictionary<string, object>();
        }

        public override object this[string name]
        {
            get
            {
                if (dictionary.ContainsKey(name))
                {
                    return dictionary[name];
                } else
                {
                    return null;
                }
            }
            set
            {
                if (!dictionary.ContainsKey(name))
                {
                    dictionary.Add(name, value);
                }
                else
                {
                    dictionary[name] = value;
                }
            }
        }

        //TODO: implement other methods here as needed to forefil the needs of the Session object. the above implementation was fine for my needs.

    }

}

Then use the code as follows:

public class MyController : TestableController { }



I found mocking to be fairly easy. Here is an example of mocking the httpContextbase (that contains the request, session and response objects) using moq.

[TestMethod]
        public void HowTo_CheckSession_With_TennisApp() {
            var request = new Mock<HttpRequestBase>();
            request.Expect(r => r.HttpMethod).Returns("GET");     

            var httpContext = new Mock<HttpContextBase>();
            var session = new Mock<HttpSessionStateBase>();

            httpContext.Expect(c => c.Request).Returns(request.Object);
            httpContext.Expect(c => c.Session).Returns(session.Object);

            session.Expect(c => c.Add("test", "something here"));            

            var playerController = new NewPlayerSignupController();
            memberController.ControllerContext = new ControllerContext(new RequestContext(httpContext.Object, new RouteData()), playerController);          

            session.VerifyAll(); // function is trying to add the desired item to the session in the constructor
            //TODO: Add Assertions   
        }

Hope that helps.




Related