Simplified Asp.net MVC Controller Testing with Moq

Writing

I wanted to follow up my last post about Asp.net MVC controller testing to show you how I have shortened up my testing code quite a bit. I am putting this up to hopefully get some feedback, but also hopefully someone else will get some good ideas from it. I am using Moq in these examples, but obviously you can use your own mocking framework.

If you didn’t see my last post about Asp.net MVC Controller testing, then you won’t be able to appreciate this:

public void UserControllerUpdateTest()
{            
    IUserRepository mockUserRepository = CreateMockUserRepository();
    UserControllerForTesting mockController = 
      CreateMockUserController(mockUserRepository);            
 
    var mockContext = new MockHttpContextContainer();
    mockContext.FormData.Add("User.UserName", "TestUser2");
    mockContext.FormData.Add("User.EmailAddress", "test2@test.com");            
 
    mockController
      .SetFakeControllerContext(mockContext.GetHttpContext());            
    mockController.Update(10);
 
    Assert.AreEqual(mockController.RedirectedAction, "List");           
 
    Assert.IsNotNull(mockController.TestingViewData);
    MvcMockHelpers.VerifyFormData(
      mockController.TestingViewData.User, mockContext.FormData);                    
}

It is quite a bit shorter now than it was previously and I have abstracted out quite a bit into separate helper classes that I use for testing. There was also a little bit of completely unneeded code in my older test that I realized I had. So, to start simplifying the code a bit the first method I created was "CreateMockUserRepository". Right now it doesn’t really do a whole lot except for return one user on a particular method. But it can be expanded later to do much more for more tests within this controller.

private IUserRepository CreateMockUserRepository()
{
    var user = new User()
    {
        Id = 10,
        UserName = "TestUser",
        EmailAddress = "test@test.com",
        ModifiedOn = DateTime.Now,
        CreatedOn = DateTime.Now
    };
 
    var mockUserRepository = new Mock<IUserRepository>();
    mockUserRepository.Expect(ur => ur.GetUser(10))
        .Returns(user);
    mockUserRepository.Expect(ur => ur.SubmitChanges());
    return mockUserRepository.Object;
}

In fact, it is likely that at some point I would just implement an entire IUserRepository stub that would implement all the methods and return dummy data. But for right now this is serving my needs. I like to only implement as much as I currently need in order to get my tests passing.

My next method is "CreateMockUserController" which I pass my mock IUserRepository into. The UserControllerForTesting class is then given a fake view engine, which I saw on this post by Derik Whittaker.

private UserControllerForTesting 
    CreateMockUserController(IUserRepository repository)
{
    UserControllerForTesting fakeController = 
      new UserControllerForTesting(repository);
    FakeViewEngine fakeView = new FakeViewEngine();
    fakeController.ViewEngine = fakeView;
    return fakeController;
}

In this method I am creating my UserControllerForTesting class which looks like this (the idea came from Phil Haack’s post):

internal class UserControllerForTesting : UserController
{
    public string RedirectedAction { get; private set; }
    public string ViewRendered { get; private set; }
    public string MasterRendered { get; private set; }
 
    public UserControllerViewData TestingViewData
    {
        get
        {
            return (UserControllerViewData)typeof(UserController)
                .GetField("_viewData", BindingFlags.NonPublic | 
                  BindingFlags.Instance).GetValue(this);
        }
    }
 
    public UserControllerForTesting(IUserRepository repository)
        : base(repository)
    {
    }
 
    protected override void RedirectToAction
       (RouteValueDictionary values)
    {
        RedirectedAction = (string)values["Action"];
    }
 
    protected override void RenderView
        (string viewName, string masterName, object viewData)
    {
        ViewRendered = viewName;
        MasterRendered = masterName;
    }
}

Here I have overridden RedirectToAction and RenderView in order to expose what is being passed to these two methods. This obviously allows us to test which views were redirected to or rendered.

So now that I have my mock repository and mock controller with a fake view. I then created a class called MockHttpContextContainer. I created this class because just using the MockHttpContext from the MVC Helper classes didn’t allow me to set more expectations on the classes that were referenced by the HttpContext. In my case I needed to be able to set a return value on the Form parameter in the HttpRequestBase class that contains the form data. Here is the MockHttpContextContainer class:

public class MockHttpContextContainer
{
    public Mock<HttpContextBase> Context { get; set; }
    public Mock<HttpRequestBase> Request { get; set; }
    public Mock<HttpResponseBase> Response { get; set; }
    public Mock<HttpSessionStateBase> SessionState { get; set; }
    public Mock<HttpServerUtilityBase> ServerUtility { get; set; }
 
    private NameValueCollection _formData;
    public NameValueCollection FormData
    {
        get
        {
            if (_formData == null)
            {
                _formData = new NameValueCollection();
                this.Request.Expect(r => r.Form).Returns(FormData);
            }
            return _formData;
        }
    }
 
    public MockHttpContextContainer()
    {
        Context = new Mock<HttpContextBase>();
        Request = new Mock<HttpRequestBase>();
        Response = new Mock<HttpResponseBase>();
        SessionState = new Mock<HttpSessionStateBase>();
        ServerUtility = new Mock<HttpServerUtilityBase>();
 
        Context.Expect(c => c.Request).Returns(Request.Object);
        Context.Expect(c => c.Response).Returns(Response.Object);
        Context.Expect(c => c.Session).Returns(SessionState.Object);
        Context.Expect(c => c.Server).Returns(ServerUtility.Object);
    }
 
    public HttpContextBase GetHttpContext()
    {
        return Context.Object;
    }        
}

As you can see here I am exposing all of the parts of my HttpContext object as Mock<T> objects which will allow me to setup expectations on them. I’ll also be able to add helper methods/properties like I have here so I can easily setup things like FormData. I also put a method called GetHttpContext to return my actual HttpContextBase object so that I don’t have to call mockContextContainer.Context.Object, it just makes it look a bit nicer. I like my code to look nice.

So, that explains about the first half of my UpdateUserControllerTest until we get to SetFakeControllerContext. This is also part of the MvcMockHelpers class that Scott Hanselman put up on this post. I believe I am using a custom overload, but it shouldn’t be anything too crazy. It just looks like this:

public static void SetFakeControllerContext
    (this Controller controller, HttpContextBase httpContext)
{
    SetFakeControllerContext
        (controller, httpContext, new RouteData());
}
public static void SetFakeControllerContext
    (this Controller controller, 
     HttpContextBase httpContext, RouteData routeData)
{            
    ControllerContext context = new ControllerContext
        (new RequestContext(httpContext, routeData), controller);
    controller.ControllerContext = context;
}

Pretty simple. Now everything is setup, finally! Now all we have to do is call our Update method on our controller class. We pass in id 10, as we have currently setup our expectations on this. We obviously didn’t have to specify 10, and we just as easily could have setup our expectation to accept any integer, this is how you do this in Moq:

mockUserRepository
    .Expect(ur => ur.GetUser(It.IsAny<int>())).Returns(user);

Then what is left is to test our state. We first test "mockController.RedirectedAction" to see if it was redirected to "List" because this is what our controller method does. We then check to make sure our view data is not null and then we pass our view data user object and our view data into a method that I created called "VerifyFormData". This method basically does the same thing as "BindingHelperExtensions.UpdateFrom" in that it takes our User object and our FormData object and loops through to make sure that the user was updated with all of our form values. It looks like this:

public static void 
    VerifyFormData(Object data, NameValueCollection values)
{
    Assert.IsNotNull(data);
 
    Type dataType = data.GetType();
    string dataName = dataType.Name;
 
    int comparisonCount = 0;
    PropertyInfo[] properties = dataType.GetProperties();
    foreach (PropertyInfo property in properties)
    {
        string key = dataName + "." + property.Name;
        if (values[key] != null)
        {
            comparisonCount++;
 
            object value = property.GetValue(data, null);
 
            TypeConverter conv = 
                TypeDescriptor.GetConverter(property.PropertyType);
            object formDataValue = values[key];
            if (conv.CanConvertFrom(typeof(string)))
            {
                try
                {
                    object convertedValue = 
                        conv.ConvertFrom(formDataValue);
                    Assert.AreEqual(convertedValue, value);
                }
                catch (FormatException)
                {
                    Assert.Fail
                        ("Format Exception Property: " + key);
                }
            }
        }
    }
    Assert.AreEqual(values.Count, comparisonCount);
}

We just loop through our properties and pull the data from our FormData. We then check to see if we used all of our form data by comparing counts. This may or may not be necessary in the future. I have yet to decide if this will get left in.

Well, for this controller method these tests are pretty good with one exception. I wanted to test to make sure that my proper method was called on my repository and I wanted to check to make sure that SubmitChanges was called on the data context. Normally we could call "mockUserRepository.VerifyAll" but this would fail to work if we expanded out our repository to have more than just the two expectations. This is because we would be setting up expectations on a bunch of different methods. I haven’t quite figured out yet which direction I am going to go with this, but I will blog about it when I do. If anyone has any ideas, please let me know.

Loved the article? Hated it? Didn’t even read it?

We’d love to hear from you.

Reach Out

Comments (2)

Leave a comment

Leave a Reply

Your email address will not be published. Required fields are marked *

More Insights

View All