views:

4345

answers:

6

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?

+8  A: 

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; }
    }
}
chadmyers
+1  A: 

Scott Hanselman has a post about how to create a file upload quickapp with MVC and discusses moking and specifically addresses "How to mock things that aren't mock friendly."

Nick DeVore
Hanselman to the rescue...
Codewerks
+2  A: 

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.

Korbin
Wow, that's a lot of work just to mock one method :) This is clearly the "too much setup" smell, and it's a result of using abstract base classes rather than interfaces as dependency seams.
chadmyers
Sure, I have seen a few projects where they put the setup code in a "helper" class and use it over and over again.
Korbin
FYI, if your test needs so much setup that you need a 'helper' class, you're going to have pain and friction down the line.
chadmyers
+13  A: 

Check out Stephen Walther's post on Faking the Controller Context:

ASP.NET MVC Tip #12 – Faking the Controller Context

[TestMethod]
public void TestSessionState()
{
    // Create controller
    var controller = new HomeController();


    // Create fake Controller Context
    var sessionItems = new SessionStateItemCollection();
    sessionItems["item1"] = "wow!";
    controller.ControllerContext = new FakeControllerContext(controller, sessionItems);
    var result = controller.TestSession() as ViewResult;


    // Assert
    Assert.AreEqual("wow!", result.ViewData["item1"]);

    // Assert
    Assert.AreEqual("cool!", controller.HttpContext.Session["item2"]);
}
David P
I had to dig for this url, so here it is: http://stephenwalther.com/blog/archive/2008/07/01/asp-net-mvc-tip-12-faking-the-controller-context.aspx
blu
+3  A: 

With MVC RC 1 the ControllerContext wraps the HttpContext and exposes it as a property. This makes mocking much easier. To mock a session variable with Moq do the following:

var controller = new HomeController();
var context = MockRepository.GenerateStub<ControllerContext>();
context.Expect(x => x.HttpContext.Session["MyKey"]).Return("MyValue");
controller.ControllerContext = context;

See Scott Gu's post for more details.

TheDeeno
A: 

Because HttpContext is static, I use Typemock Isolator to mock it, Typemock also has an Add-in custom built for ASP.NET unit testing called Ivonna .