Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Question
Wednesday, October 1, 2008 3:40 PM
I recently changed my application to implement a custom ModelBinder. So my controller action now looks like this:
[AcceptVerbs("POST")]public ActionResult New(Employee employee, string submit)
{
if (submit == "Cancel")
return RedirectToAction("EmployeeManagement", "Home");
dropDownListPopulator.PopulateEmployeeDropDownLists(ViewData, employee);
if(!ViewData.ModelState.IsValid)
{
TempData["Message"] = "Please correct Invalid Input";return View("Edit", employee);
}
I am having trouble unit testing because of the line ViewData.ModelState.IsValid. I would like to just mock everything out so that IsValid returns false. First I tried the following in my unit test:
employeeController.ViewData.ModelState = mockModelState;
but the ModelState property of ViewData is readonly so that wouldn't work. So then I tried to mock the ViewData and do the following:
employeeController.ViewData = mockViewData;
Expect.Call(mockViewData.ModelState).Return(mockModelState);
but it says that the ModelState property is not virtual.
So how do I mock ModelState?
All replies (14)
Wednesday, October 1, 2008 7:12 PM âś…Answered
You don't need to mock it. Just add an error.
employeeController.ViewData.ModelState.AddModelError("dummy", "dummy", "dummy")
IsValid is now false.
Tuesday, October 7, 2008 1:02 PM
While you are correct that that would work in this situation, I still think the larger issue needs to be addressed. How do I substitute for ModelState?
Tuesday, October 7, 2008 1:08 PM
Tell me what situation it wont work in?
Tuesday, October 7, 2008 2:41 PM
Maybe that was poor wording on my part. It's not that I am worried about it NOT working, I just think that there may be some situations where it would be easier to mock it. Isn't this what ASP.NET MVC is all about, making things testable and breaking dependancies?
Tuesday, October 7, 2008 4:58 PM
mockable != testable
if you come up with a scenario where you can't test your action because of the way ModelState is implemented, that's a different matter!
Tuesday, October 7, 2008 9:49 PM
I never said mockable = testable. I said, I can foresee a situation where I would prefer to mock out the ModelState. I also said, it seems like this is an uncharacteristic coupling in the MVC Framework. I just asked if it was possible to substitute for it. Your answer of "you don't have to" doesn't really answer my question. Yes, you helped me get around the immediate problem, and I thank you for that. But that doesn't mean that I shouldn't be able to do what I am asking. If you can give me a valid reason why it is this way, or a way to do what I am asking, great. But what you are basically doing is telling me it is a stupid question. That is a matter of opinion and I disagree. Let's see if anyone else can weigh in on the subject.
Wednesday, October 8, 2008 1:04 AM
But that doesn't mean that I shouldn't be able to do what I am asking.
I think the point of the above discussion was that there's no need to, hence there's no mechanism to provide this. The model state is a fundamental part of the view data dictionary, so you "mock" the model state by replacing the dictionary itself.
But what you are basically doing is telling me it is a stupid question.
Not a stupid question. :) But given my previous paragraph, why would you want to "mock" the model state when it's far easier just to replace the dictionary or to modify it directly?
Wednesday, October 8, 2008 11:00 AM
levib, thanks for your response. How do I "replace the dictionary"?
Wednesday, October 8, 2008 12:17 PM
levib, thanks for your response. How do I "replace the dictionary"?
The controller's ViewData property is settable. So instead of saying ViewData.ModelState = ..., you'd create a ViewDataDictionary that has model errors, then write ViewData = [this new object].
Wednesday, October 8, 2008 12:43 PM
This may be a silly example, but hopefully it moves the discussion forward:
The following code is in my Controller Action:
if (ViewData.ModelState.Count > 10)
{
ViewData["Message"] = "Too many errors to continue";return View();
}
Here is a test with mocking and a test wtihout mocking:
[Test]public void Test11ModelStateErrorsWithMock()
{
HomeController homeController = new HomeController();
MockRepository mockRepository = new MockRepository();
ModelStateDictionary mockModelStateDictionary = (ModelStateDictionary)mockRepository.DynamicMock(typeof(ModelStateDictionary));
homeController.ViewData.ModelState = mockModelStateDictionary;
Expect.Call(mockModelStateDictionary.Count).Return(11);
mockRepository.ReplayAll();
ViewResult viewResult = (ViewResult)homeController.About();
mockRepository.VerifyAll();
Assert.AreEqual("Too many errors to continue", viewResult.ViewData["Message"]);
}
[Test]public void Test11ModelStateErrorsWithoutMock()
{
HomeController homeController = new HomeController();
homeController.ViewData.ModelState.AddModelError("1", "FakeValue", "FakeException");
homeController.ViewData.ModelState.AddModelError("2", "FakeValue", "FakeException");
homeController.ViewData.ModelState.AddModelError("3", "FakeValue", "FakeException");
homeController.ViewData.ModelState.AddModelError("4", "FakeValue", "FakeException");
homeController.ViewData.ModelState.AddModelError("5", "FakeValue", "FakeException");
homeController.ViewData.ModelState.AddModelError("6", "FakeValue", "FakeException");
homeController.ViewData.ModelState.AddModelError("7", "FakeValue", "FakeException");
homeController.ViewData.ModelState.AddModelError("8", "FakeValue", "FakeException");
homeController.ViewData.ModelState.AddModelError("9", "FakeValue", "FakeException");
homeController.ViewData.ModelState.AddModelError("10", "FakeValue", "FakeException");
homeController.ViewData.ModelState.AddModelError("11", "FakeValue", "FakeException");
ViewResult viewResult = (ViewResult)homeController.About();
Assert.AreEqual("Too many errors to continue", viewResult.ViewData["Message"]);
}
In my opinion, the test with the mock is less verbose and shows the intent better. It is easier to change (for example if the requirement changed to having a limit of 20 errors).
Wednesday, October 8, 2008 1:03 PM
That would be fine if dictionaries in general were meant to be mocked, but they're not. They're generally meant to be fully self-describing data storage objects, and in general they're not extensible because they don't have to be. A cursory glance through the Dictionary<TKey, TValue> API shows that there are no virtual methods or properties on that type. However, because dictionaries are fully self-describing, they are testable, even though they're not mockable.
As an aside, your action + test are incorrect. The number of entries in the ModelStateDictionary does not correspond to the number of errors, as an entry can have zero or more errors associated with it.
Wednesday, October 8, 2008 3:41 PM
levib,
Thanks again for your excellent explanation. I actually did know about the 0 to many of ModelState items to errors but was simplifying to make my point. But maybe this does point back to where my problem is. For example, how would I ask ModelState for the total number of errors? Do I need to cycle through all the keys and add up the number of errors for each one? Maybe I am looking for ModelState to be something more feature rich than a Dictionary? Maybe feature rich isn't the right word either... just less generic... or more specific however you want to say it.
Thanks,
Bradley
Wednesday, October 8, 2008 3:52 PM
For example, how would I ask ModelState for the total number of errors? Do I need to cycle through all the keys and add up the number of errors for each one?
Yes. Though in general, I'd recommend against placing too much faith in the number of errors reported unless you have very fine control over how the ModelStateDictionary is populated. For example, if a user types "dog" into a textbox and we try parsing that value as a positive integer, how many errors would be reported? One of "The value 'dog' is not a valid integer." or "You must type a positive number."? Perhaps both? What about if a user is registering for a website and types in a password but forgets to fill out the 'confirm password' box? One, two, perhaps more? Obviously, if you're populating the MSD yourself, you have control over this, but otherwise I'd be wary about depending on the number of entries and just sticking to whether there was an error at all.
Maybe I am looking for ModelState to be something more feature rich than a Dictionary?
Maybe. [:D] This is why we're trying to be fairly open regarding feedback and design decisions. If there's something that's repetitive, unnecessarily complex, or makes you think "gee, the framework really should be doing this for me," please let us know!
Wednesday, October 8, 2008 4:21 PM
levib,
Thanks again. My feedback in this case is that the ModelState is a little confusing to work with. Even the fact that ViewData.ModelState doesn't return a ModelState but a ModelStateDictionary is a bit confusing at first. I guess it is awkward to make ModelState plural though, as in ViewData.ModelStates.