ModelState is always returning null in my unit tests. I was hoping someone could tell me why.
Given the following controller:
public class TestController : Controller
{
public ViewResult Index()
{
return View();
}
}
My test gets null for ModelState with this test:
public void ModelState_Is_Not_Null()
{
TestController controller = new TestController();
var result = controller.Index();
// This test is failing:
Assert.IsNotNull(controller.ViewData.ModelState);
}
If I change the controller to return a new ViewResult() I don't get null:
public class TestController : Controller
{
public ViewResult Index()
{
return new ViewResult();
}
}
But... IsValid() returns true when it shouldn't if I do it this way:
public class TestController : Controller
{
public ViewResult Index()
{
ModelState.AddModelError("Test", "This is an error");
return new ViewResult();
// I don't get null in the test for ModelState anymore, but IsValid()
// returns true when it shouldn't
}
}
I think I'm doing something fundamentally wrong here and I don't know what. Could anyone point me in the right direction?
Thanks for checking that, Darin.
I had the MVC 1 RC and MVC 2 RC 2 versions installed. I uninstalled both of them, installed MVC 1 and now everything is behaving as expected. The test doesn't fail.
Related
Inside controller I have view which returns simple object to the view.
public ActionResult SomeAction(int?id)
{
MyModel model = new MyModel();
return View(model);
}
how can I unit test this controller in order to check ViewResult,
basically to
check if view is initialized? Basically how can I mock this MyModel
inside my unit test?
[Test]
public void Can_Open_SomeAction()
{
// controller is already set inside `SetUp` unit step.
ViewResult res = this.controller.SomeAction() as ViewResult;
Assert.IsNotNull(res);
}
Update:
public ActionResult SomeAction(int?id)
{
MyModel model = new MyModel();
this.PopulatePageCombos(id);
return View(model);
}
The way you set this up, I presume you simply want to see if the model is not null:
[Test]
public void Can_Open_SomeAction()
{
ViewResult res = this.controller.SomeAction() as ViewResult;
Assert.IsNotNull(res);
var model = result.Model as MyModel;
Assert.IsNotNull(model);
}
Mocking would only make sense in a context where you'd get that model from an underlying interface, for example if you had:
public ActionResult SomeAction(int?id)
{
MyModel model = _myModelQuerier.Fetch(id.Value);
return View(model);
}
then you could get around something like
var modelQuerierMock = MockRepository.GenerateMock<IMyModelQuerier>();
modelQuerierMock.Stub(x => x.Fetch(Arg<int>.Is.Anything)).Return(new MyModel(2, "product"));
inside your test class
It appears that something has changed with the release version of MVC4 that is causing the ExecuteResult method in a custom actionresult to not be invoked when the action result is tested from a unit test.
Here is a very contrived example that works in MVC3 and earlier versions of MVC4. Execute result is never "executed" when ran from a unit test. What am i missing here? Anyone else see this behavior?
Action result
public class SomeActionResult : ActionResult
{
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("null context");
}
var view = new ViewResult {ViewName = "index"};
view.ExecuteResult(context);
}
}
Controller Action
[HttpPost]
public ActionResult Index(string something)
{
return new SomeActionResult();
}
Unit Test (Using MVCContrib)
[Test]
public void ShouldWork_but_doesnt_in_mvc4()
{
var controller = new HomeController();
var result = controller.Index("test");
result.AssertViewRendered();
}
Here is a very contrived example that works in MVC3 and earlier versions of MVC4.
You must have mistaken something. This won't work in MVC 3, 2, 1 either. And it is expected. Because a unit test means that you are unit testing something in isolation. So you have one unit test for the controller action and another to test your custom action result.
It is not the Index action that is invoking the ExecuteResult result method on the action result. This happens higher in the MVC execution pipeline during the execution of a user request. In your unit test you are simply calling the Index method.
So in order to unit test this controller action you simply assert that it returns an action result of the proper type:
[Test]
public void Ensure_That_Index_Action_Return_SomeActionResult()
{
// arrange
var controller = new HomeController();
// act
var result = controller.Index("test");
// assert
result.AssertResultIs<SomeActionResult>();
}
It is in another unit test of the SomeActionResult that you will manually invoke the ExecuteResult method and assert that this custom action result used a ViewResult.
Also it would seem more appropriate to have your custom action result derive from ViewResult rather than manually instantiating a ViewResult inside the ExecuteResult method and setting the ViewName:
public class SomeActionResult : ViewResult
{
public override void ExecuteResult(ControllerContext context)
{
this.ViewName = "Index";
base.ExecuteResult(context);
}
}
I am using FluentValidation in my MVC project and have the following model and validator:
[Validator(typeof(CreateNoteModelValidator))]
public class CreateNoteModel {
public string NoteText { get; set; }
}
public class CreateNoteModelValidator : AbstractValidator<CreateNoteModel> {
public CreateNoteModelValidator() {
RuleFor(m => m.NoteText).NotEmpty();
}
}
I have a controller action to create the note:
public ActionResult Create(CreateNoteModel model) {
if( !ModelState.IsValid ) {
return PartialView("Test", model);
// save note here
return Json(new { success = true }));
}
I wrote a unit test to validate the behavior:
[Test]
public void Test_Create_With_Validation_Error() {
// Arrange
NotesController controller = new NotesController();
CreateNoteModel model = new CreateNoteModel();
// Act
ActionResult result = controller.Create(model);
// Assert
Assert.IsInstanceOfType(result, typeof(PartialViewResult));
}
My unit test is failing because it doesn't have any validation errors. This should succeed because model.NoteText is null and there is a validation rule for this.
It appears that FluentValidation isn't running when I run my controller test.
I tried adding the following to my test:
[TestInitialize]
public void TestInitialize() {
FluentValidation.Mvc.FluentValidationModelValidatorProvider.Configure();
}
I have this same line in my Global.asax to tie up the validators to the controllers automatically...but it doesn't appear to be working in my unit test.
How do I get this working correctly?
That's normal. Validation should be tested separately from controller actions, like this.
And to test your controller action simply simulate a ModelState error:
[Test]
public void Test_Create_With_Validation_Error() {
// Arrange
NotesController controller = new NotesController();
controller.ModelState.AddModelError("NoteText", "NoteText cannot be null");
CreateNoteModel model = new CreateNoteModel();
// Act
ActionResult result = controller.Create(model);
// Assert
Assert.IsInstanceOfType(result, typeof(PartialViewResult));
}
A controller shouldn't really know anything about fluent validation. What you need to test here is that if there is a validation error in the ModelState your controller action behaves correctly. How this error was added to the ModelState is a different concern that should be tested separately.
I've never used any Mock frameworks and actually new to ASP.NET MVC, testing and all this related stuff.
I'm trying to figure out how to use Moq framework for testing, but can't make it work. that's what I have at the moment: My repository interface:
public interface IUserRepository {
string GetUserEmail();
bool UserIsLoggedIn();
ViewModels.User CurrentUser();
void SaveUserToDb(ViewModels.RegisterUser viewUser);
bool LogOff();
bool LogOn(LogOnModel model);
bool ChangePassword(ChangePasswordModel model);
}
My Controller constuctor, I'm using Ninject for injection, it works fine
private readonly IUserRepository _userRepository;
public HomeController(IUserRepository userRepository) {
_userRepository = userRepository;
}
Simplest method in controller:
public ActionResult Index() {
ViewBag.UserEmail = _userRepository.GetUserEmail();
return View();
}
And my test method:
[TestMethod]
public void Index_Action_Test() {
// Arrange
string email = "test#test.com";
var rep = new Mock<IUserRepository>();
rep.Setup(r => r.GetUserEmail()).Returns(email);
var controller = new HomeController(rep.Object);
// Act
string result = controller.ViewBag.UserEmail;
// Assert
Assert.AreEqual(email, result);
}
I assume that this test must pass, but it fails with message Assert.AreEqual failed. Expected:<test#test.com>. Actual:<(null)>.
What am I doing wrong?
Thanks
Simple - you do not do Act part correctly. Fisrt you should call Index() action of the controller, and then Assert ViewBag.UserEmail correctness
// Act
controller.Index();
string result = controller.ViewBag.UserEmail;
By the way, advice - Using ViewBag is not the good practice. Define ViewModels instead
I use this pattern all over the place to grab data from the database and display a view:
public ActionResult Index(int? id)
{
RequestViewModel model;
model = this.ClientRepository.GetRequest(id);
return View("~/Views/Requests/Index.aspx", model);
}
If the repository returns null, which is the case if the record does not exist, then my page craps out and throws an error because the model is null.
I’d like to show a friendly “the requested record cannot be found” message instead of the yellow page of death or a generic “an error occurred” page.
What’s the recommended pattern to handle “normal” errors as opposed to unhandled exceptions?
Thanks,
Rick
You could write an action filter:
public class NullModelCheckerAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
var viewResult = filterContext.Result as ViewResultBase;
if (viewResult != null && viewResult.ViewData.Model == null)
{
// If the action selected a view to render and passed a null model
// render the NotFound.aspx view
var result = new ViewResult();
result.ViewName = "~/Views/Errors/NotFound.aspx";
filterContext.HttpContext.Response.StatusCode = 404;
filterContext.Result = result;
}
}
}
And then decorate your base controller (that all your controllers derive from) with this attribute:
[NullModelChecker]
public class BaseController: Controller
{ }
This way your current code stays untouched.
--
UPDATE:
In ASP.NET MVC 3 you could register your action filter globally without even decorating your base controller with it. Simply add the following to your Application_Start in Global.asax:
GlobalFilters.Filters.Add(new NullModelCheckerAttribute());
I'm not familiar with ASP.NET MVC. I'm familiar though with Spring MVC.
Why can't you just put a simple if-else condition? Like this one:
public ActionResult Index(int? id)
{
RequestViewModel model;
model = this.ClientRepository.GetRequest(id);
if (model == null) {
return View("~/Views/Requests/FriendlyError.aspx");
}
return View("~/Views/Requests/Index.aspx", model);
}