43

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?

7 Answers 7

44

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"]);
}
Sign up to request clarification or add additional context in comments.

7 Comments

I had to dig for this url, so here it is: stephenwalther.com/blog/archive/2008/07/01/…
It might not be obvious, but FakeControllerContext is a custom class. You can see the source for that here: stephenwalther.com/Downloads/Tips/Tip12/Tip12.zip
The source link above seems to be dead.. anyone have a copy?
Jacob, you are a Hero Among Men. The code still works wonderfully, as long as you use Controllerbase instead of Icontroller
|
14

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

Comments

10

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.

Comments

5

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.

3 Comments

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.
Sure, I have seen a few projects where they put the setup code in a "helper" class and use it over and over again.
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.
2

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."

Comments

2

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

Comments

0

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 .

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.