Posted on 3/24/2008 4:13:16 PM by Justin Etheredge
So, in my previous post about testing MVC controller actions with Moq I left out any way to verify the calls on my MockUserRepository class. I did this because I wanted to have one MockUserRepository that I used for all tests in the UserControllerTest test class (most would refer to it as a fixture, but visual studio test likes to be different). So I factored out a class that was cleverly called "CreateMockUserRepository". The original version of the method looks like this:
private Mock<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;
}
This all worked fine and great, and if I called "VerifyAll" on my Mock<IUserRepository> then everything would work great. But what if I wanted to reuse this method for more tests. I wanted my mock to be useful in multiple places. So, lets say I changed it to look like this:
private Mock<IUserRepository> CreateMockUserRepository()
{ var user = new User()
{ Id = 10,
UserName = "TestUser",
EmailAddress = "test@test.com",
ModifiedOn = DateTime.Now,
CreatedOn = DateTime.Now
};
var users = new List<User>()
{ new User() { Id = 10, UserName = "TestUser", EmailAddress = test@test.com,
ModifiedOn = DateTime.Now,
CreatedOn = DateTime.Now },
new User() { Id = 11, UserName = "TestUser2", EmailAddress = test2@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.GetUsers())
.Returns(users);
mockUserRepository.Expect(ur => ur.SubmitChanges());
return mockUserRepository;
}
Here we have added a List<User> to hold multiple users for use with our "GetUsers" call. And at the bottom we set our expectation. So, this will now make my test for a single user edit fail. It will fail because we have setup an expectation on "GetUsers" that we never call. One solution would be to just not call "VerifyAll" knowing that my expectations aren't going to be met, but this is obviously not ideal. Another idea would be to just create my test data and setup expectations inside of each test. I'm not a fan of this either because I have more than a few methods that need to be setup and I'd rather not have to set them up in each and every test. A third option would be for me to just create an IUserRepository stub and just use my own state variables to check that certain methods had been called. I would also rather not do this because I don't see why I should mimic the behavior that my mocking framework already provides for me.
So what is a programmer to do?
Well, download the source and start futzing. I decided that my best course of action would be to somehow allow groups for "Verifiable" expectations. So, I figured that passing in a string was good enough for me now. I went into the Moq source and found that adding a parameter on the Verifiable and Verify methods was actually quite easy. I then copied my custom build over my old Moq dlls and I changed the last few lines in the above method to look like this:
var mockUserRepository = new Mock<IUserRepository>();
mockUserRepository.Expect(ur => ur.GetUser(10))
.Returns(user).Verifiable("SingleUser");mockUserRepository.Expect(ur => ur.GetUsers())
.Returns(users).Verifiable("MultiUser");mockUserRepository.Expect(ur => ur.SubmitChanges())
.Verifiable();
You'll notice the "Verifiable" calls on the end of the "GetUser" and "GetUsers" now have string parameters where I am passing values into each of them. This add those verifications into that particular group. I could have multiple verifications in any of the groups. Then the "Verifiable" on "SubmitChanges" doesn't have any parameters so it acts just like it always has. The idea here is that now in my single user edit, I can now make this call:
mockUserRepository.Verify("SingleUser");
And only my "GetUser" and "SubmitChanges" verifications will be tested, my "MultiUser" verification will be ignored for this test. And that is it!
So, does anyone else think that this might be a good addition to Moq? Does anyone else have a better way that I could have implemented this? (I've been working with Moq for all of 3 days now, so I could be missing something elementary) If so, then please let me know. Otherwise I may pull down the latest dev source and work my changes into it, then submit a patch and see if those guys might work it into a future version.
Posted on 3/23/2008 4:01:23 AM by Justin Etheredge
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.
Posted on 3/20/2008 2:03:00 AM by Justin Etheredge
Update: I have put up a new post here that shows how I have simplified some of my testing. If you like it please kick it so that others will see the follow up post!
Here is my "Update" action on a project that I am working on:
public void Update(int id)
{
_viewData.User = _userRepository.GetUser(id);
BindingHelperExtensions
.UpdateFrom(_viewData.User, Request.Form);
_userRepository.SubmitChanges();
RedirectToAction("List");
}
And here is my test for it (using Moq, I wanted to try it out. This uses the Moq MvcMockHelpers from Scott Hanselman's post):
public void UserControllerUpdateTest()
{
RouteTable.Routes.Add(
new Route("[controller]/[action]", new MvcRouteHandler()));
var mockUserRepository = new Mock<IUserRepository>();
var user = new User()
{
Id = 10,
UserName = "TestUser",
EmailAddress = "test@test.com",
ModifiedOn = DateTime.Now,
CreatedOn = DateTime.Now
};
mockUserRepository.Expect(ur => ur.GetUser(10))
.Returns(user);
mockUserRepository.Expect(ur => ur.SubmitChanges());
var fakeController =
new UserControllerForTesting(mockUserRepository.Object);
FakeViewEngine fakeView = new FakeViewEngine();
fakeController.ViewEngine = fakeView;
var context = new MockHttpContextContainer();
var formData = new NameValueCollection();
var userNameFormData = "TestUser2";
var emailAddressFormData = "test2@test.com";
formData.Add("User.UserName", userNameFormData);
formData.Add("User.EmailAddress", emailAddressFormData);
context.Request.Expect(r => r.Form)
.Returns(formData);
var routeData = new RouteData();
routeData.Values.Add("Action", "Add");
routeData.Values.Add("Controller", "Home");
fakeController
.SetFakeControllerContext(context.Context.Object,
routeData);
fakeController.Update(10);
mockUserRepository.VerifyAll();
Assert.AreEqual(fakeController.RedirectedAction, "List");
var viewData = fakeController.TestingViewData;
Assert.IsNotNull(viewData);
Assert.IsNotNull(viewData.User);
Assert.AreEqual(viewData.User.UserName,
userNameFormData);
Assert.AreEqual(viewData.User.EmailAddress,
emailAddressFormData);
}
And that doesn't even include any of my helper objects or methods. Anyone see a problem with this? I am going to have to put some serious effort into streamlining this. I know that there is quite a bit I can abstract out across different controllers and actions so we will see how this goes. Feel free to provide feedback if you like. So far though I am really having fun digging into this, I can't believe I waited this long!