ModelState.IsValid does not validate model - c#

My Model class is as follow :
public class PostInputViewModel
{
[Required]
[MinLength(1)]
[MaxLength(125)]
public string Title { get; set; }
[Required]
[AllowHtml]
[Display(Name="Content")]
public string Content { get; set; }
}
and controller is as follow :
[HttpPost]
public ActionResult Write(PostInputViewModel input)
{
if (!ModelState.IsValid)
return View(input);
var post = new Post
{
Title = input.Title,
Content = input.Content,
DateCreated = DateTime.Now,
DateModified = DateTime.MaxValue,
};
dbContext.Posts.Add(post);
dbContext.SaveChanges();
return RedirectToAction("Index", "Home");
}
When I run web application by clicking F5, and if I don't input title and content value, ModelState.IsValid is false, However if I test controller class with unit test case, ModelState.IsValid is always true. The test case is as follow :
[TestMethod]
public void write_should_validate_model()
{
var input = new PostInputViewModel();
input.Title = null;
input.Content = null;
var actionResult = controller.Write(input) as ViewResult;
Assert.IsFalse(actionResult.ViewData.ModelState.IsValid);
}
Am I missing something?
Thanks in advance.

If you want to have your controller try to validate the model, you can call the TryValidateModel method before your assert:
controller.TryValidateModel(input);
But I agree that you'd really only be testing the validation attributes. It might be OK, though; it would validate that your model has the expected attributes applied.

Validation of model doesn't happen in the Controller. It happens before the model is passed to the controller.
Notice the controller action is only 'testing' whether the model is valid. Who is validating the model in your test case? Nothing!
You can do the validation using Validator class of .NET however in that case you would be testing .NET validation. This is one of the common mistakes people make while writing unit tests. They test 3rd party code instead of their own.
If you really want to test that you have applied the correct validation attributes to the class then you can simply reflect your class and check for attributes on properties. This way you would be skipping the .NET validation layer and your test will only fail if you miss an attribute.

Validation actually happens before the Write method on your controller is called, which populates the ModelState property.
Your unit test isn't really testing the controller in my opinion (if that is in fact what you're trying to do).
A true controller test would look something like this:
[TestMethod]
public void write_should_validate_model()
{
controller.ModelState.AddModelError("Title", "Empty"); //These values don't really matter
var actionResult = controller.Write(new PostInputViewModel()) as ViewResult;
//Assert that the correct view was returned i.e. Not Home/Index
//Assert that a new post has not been added to your Mock Repository
}

Related

satisfying the validation before checking the validation

suppose I have the following class
class A
{
[Required]
public string Name {get; set;}
[Required]
public string NickName {get; set;}
[Required]
public string UserId {get; set;}
}
and from the form I am passing only the Name and NickName to controller and before checking the model state simply I assign the user id to the UserId property as below
[HttpPost]
public IActionResult Save(A model)
{
model.UserId = User.GetLoggedInUserId<string>();
if (!ModelState.IsValid)
{
return View(model);
}
}
even though I have assign the user id before checking the model state it still returns the validation state false and complaining the for the user id. one way to come out of this problem is to create a view model which makes things more complex, because of assigning the values from view model to class it self.
any idea how to solve this.
Note: the question is not only for the User Id in the class there maybe other properties as well that may not be passed from the form to controller and the values maybe assigned to them from controller
You could try to remove the 'UserId' from the model validation before calling ModelState.IsValid. Code like this:
[HttpPost]
public IActionResult CreateA(A a)
{
var state1 = ModelState.IsValid; // false
ModelState.Remove("UserId"); //using Remove method to remove the specified object from the model-state dictionary.
var state2 = ModelState.IsValid; // true
a.UserId = "SN1001";
if (ModelState.IsValid)
{
var data = a.UserId;
}
return RedirectToAction(nameof(Index));
}
The screenshot as below:
Besides, you could also try to use the TryValidateModel() method to validate the model again in the controller, code like this:
[HttpPost]
public IActionResult CreateA(A a)
{
var state1 = ModelState.IsValid; // false
ModelState.Remove("UserId");
a.UserId = "SN1001";
if (!TryValidateModel(a, nameof(a)))
{
var state2 = ModelState.IsValid;
}
if (ModelState.IsValid)
{
var data = a.UserId;
}
return RedirectToAction(nameof(Index));
}
The result like this:
Reference: Model State Rerun validation
Edit
[Note] If the ModelState.IsValid is false first, before rerun validation using the TryValidateModel method, we have to remove the error from the ModelState.
You can also pass the value of UserId field using a hidden field with a default value, like:
#Html.HiddenFor(m => m.UserId, new { #Value = User.Identity.Name });
This answer might help What does ModelState.IsValid do?
ModelState.IsValid indicates if it was possible to bind the incoming values from the request to the model correctly and whether any explicitly specified validation rules were broken during the model binding process.
There is no way to solve this. If you don't need this validation, then remove it altogether with the attributes and add the appropriate value handling logic with code.
The isValid will just validate the state on binding time and that's that.

UnitTest is expected ModelState to be invalid but it isn't

I have a .NetCore MVC project and I'm trying to unit test my controller.
ViewModel (Note the [Required] attribute):
public class Bank : BaseObject
{
[Required]
[DisplayName("Bank")]
public string Name { get; set; }
}
Controller Action:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Name")] Bank bank)
{
if (ModelState.IsValid)
{
await _bankService.Insert(bank);
return RedirectToAction(nameof(Index));
}
return View(bank);
}
Unit Test:
[Test]
public async Task When_PostingCreateBankThatIsInvalid_ThenBankIsReturned()
{
var bank = new Bank
{
//nothing set = invalid state - this is what we want
};
var controller = new BanksController(null);
var response = await controller.Create(bank);
}
I was expected the line if (ModelState.IsValid) to return false as Name is required - then I was going to perform my Asserts based on that. But the result is true and so we try to insert a bank.
What am I doing wrong here? I've Googled but I can only find answers that don't relate to unit tests. I thought the ModelState upheld the [Required] attribute?
If I test using the UI I am unable to create a bank without a name - it never even reaches the controller (as expected).
According to the docs:
Model validation occurs prior to each controller action being invoked
so I believe the problem lies with how I'm creating the BanksController. Am I approaching this test in the wrong manner? I wonder if I should just be setting the ModelState to invalid in the test...?
Attributes are metadata that is only recognized by the framework at run time and not during a unit test as they are actually read by the model binder that when the application is running.
For the state to change you will either have to run an integration test where the necessary parts of the framework are available to update the model state,
or update the model state manually since model binding isn't running (though an integration test would be used to exercise model binding) in the controller so that the test behaves as expected when being exercised.
[Test]
public async Task When_PostingCreateBankThatIsInvalid_ThenBankIsReturned() {
//Arrange
var bank = new Bank
{
//nothing set = invalid state - this is what we want
};
var controller = new BanksController(null);
controller.ModelState.AddModelError("Name","Name required");
//Act
var response = await controller.Create(bank);
//Assert
response.Should().NotBeNull()
.And.BeOfType<ViewResult>();
var viewResult = response as ViewResult;
viewResult.Model.Should().Be(model);
}
Don't try to test model validation or model binding in your unit tests - just test your action method's behavior when confronted with a particular ModelState value.
Reference Test controller logic in ASP.NET Core

Do I need to active .NET Core MVC Model Validation or is it activated by default

Testing my web API (nuget package Microsoft.AspNetCoreAll 2.0.5) I run into strange issues with the model validation using annotations.
I have (for example) this controller:
[HttpPost]
public IActionResult Create([FromBody] RequestModel request)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
// create
request.Name.DoSomething();
return Created(...);
}
I defined my RequestModel as follows:
public class RequestModel
{
[Required]
public string Name {get; set};
}
My problem although I defined RequestModel.Name as [Required] it is null (if Name is not present in the json from the body. Which I thought should not happen since it is marked as [Required] and be automatically appear as ModelState error.
Given this link to the specs they use Bind(....).
So my question?
Do I have to enable it everytime or should it work out of the box or how is it intended to be used?
If I annotate it with [Required] I would assume that at least ModelState.IsValid returns false if it is not present.
Using Bind in the link seems a bit complicated for me in cases where I have multiple objects nested into each other.
Edit 1: created a MVC data validation test bed
To better visualize what I mean and so everyone can easily experiment on their own I created the small demo .NET Core MVC data validation test bed on GitHub.
You can download the code, start it with VS 2017 and try it out your own using the swagger ui.
Having this model:
public class StringTestModel2
{
[Required]
public string RequiredStringValue { get; set; }
}
And testing it with that controller:
[HttpPost("stringValidationTest2")]
[SwaggerOperation("StringValidationTest2")]
public IActionResult StringValidationTest2([FromBody] StringTestModel2 request)
{
LogRequestModel("StringValidationTest2", request);
if (!ModelState.IsValid)
{
LogBadRequest(ModelState);
return BadRequest(ModelState);
}
LogPassedModelValidation("StringValidationTest2");
return Ok(request);
}
The results are far way from expected:
Giving a null (not the string "null") is allowed and return 200 OK
Giving an int is allowed an returns 200 OK (it gets converted to a string)
Giving a double is allowed and returns 200 OK (if possible it gets converted to string, if not convertible (mixing points and semicolons return 400 Bad Request)
if you just send empty curly brackets and leave RequiredStringValue undefined it passes and returns 200 OK (with the string as null).
Leaving me (for now) with one of the follwoing conclusions:
either MVC data validation does not work out of the box
either does not work as expected (if one marks a property as required it should be made sure it is there)
either MVC data validation is broken
either MVC data validation is completly useless
we are missing some important point (like the Bind[])
You get ModelValidation automatically as part of using/deriving from controller (I believe it is in the MVC middleware) but, unfortunately, this does not include null checks. So you need to explicitly check the parameter is NULL as well as the ModelState check.
[HttpPost]
public IActionResult Create([FromBody] RequestModel request)
{
if (request == null || !ModelState.IsValid)
{
return BadRequest(ModelState);
}
...
I assume you use
services.AddMvc();
so it should work by default.
But it doesn't work just as you expect: instead of returning 400 status code it invalidates model state and lets you manage action result.
You can create an attribute class to automatically return "Bad request"
internal class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(
new ApiError(
string.Join(" ",
context.ModelState.Values
.SelectMany(e => e.Errors)
.Select(e => e.ErrorMessage))));
}
}
}
where ApiError is a custom ViewModel for error results.
Now you can mark controllers or actions with this attribute to achieve a behavior you expect to have by default.
If you want this behavior for all methods just change your AddMvc line to something like this:
services.AddMvc(config => config.Filters.Add(new ValidateModelAttribute()));
After further experimenting around I found the answer.
Does the data validation need to be activated?
Answer: it depends on your configure services method:
No, it does not need to be activated if you use
services.AddMvc();
Yes, it needs to be activated if you use
services.AddMvcCore()
.AddDataAnnotations(); //this line activates it
This article brought me to the answer.

Why Controller.UpdateModel doesn't validate model with globalized validation attributes from a unit test?

I created an ASP.NET MVC 5 application, with the following model class using a globalized Required validation attribute on its Email property:
public class Person
{
[Required(ErrorMessageResourceType = typeof(Resources),
ErrorMessageResourceName = "EmptyEmailError")]
public string Email { get; set; }
}
The Create POST action method of the PersonController class is:
[HttpPost]
public ActionResult Create(FormCollection formData)
{
Person newPerson = new Person();
UpdateModel(newPerson, formData);
if (ModelState.IsValid) {
// code to create the new person
return View("Index", newPerson);
}
else {
return View();
}
}
And the test case for verifying that a person with an empty e-mail address is not created is:
[TestMethod]
void Create_DoesNotCreatePerson_WhenEmailIsEmpty()
{
// arrange
FormCollection person = new FormCollection() { { "Email", string.Empty } };
PersonController controller = new PersonController();
controller.ControllerContext = new ControllerContext();
// act
controller.Create(person);
// assert
Assert.IsFalse(controller.ModelState.IsValid);
}
With this code the normal execution works fine. However, the test doesn't pass, which means that UpdateModel is returning true and thus not validating the person model correctly. The TryUpdateModelmethod doesn't work too.
But if I remove the ErrorMessage arguments from the Required validation attribute in the Person class (i.e. leaving it just as [Required]), then the test passes.
So I don't know why UpdateModel doesn't validate the globalized Person model when I call the Create action method from the test case.
I'm happy to see you are writing unit tests for your code :)
First, why don't you just pass Person in the first place, do you really need FormCollection?
[TestMethod]
void Create_DoesNotCreatePerson_WhenEmailIsEmpty()
{
// arrange
FormCollection person = new FormCollection() { { "Email", string.Empty } };
PersonController controller = new PersonController();
controller.ControllerContext = new ControllerContext();
// third, you might need to add this (haven't tested this)
controller.ModelState.AddModelError("Email", "fakeError");
// act
controller.Create(person);
// assert
Assert.IsFalse(controller.ModelState.IsValid);
}
Fourth and most important. I dont' think you should test like this. You want to test the outcome for different scenarios. That means you want to test the return value for the method. When you say Assert.IsFalse(controller.ModelState.IsValid); you basically say you don't trust Microsoft's code and you want to test their code. I can assure you they have unit tests on their code and you do not need to test it again ;)
Instead, test what view is returned and what object is returned from the action result. Here is a complete article for how to unit test this -> https://msdn.microsoft.com/en-us/library/gg416511(VS.98).aspx (See "Creating a test for retrieving contacts"). Hope this helps!
EDIT (Answer to comments):
Sory for the long response time.
I removed to "second".
If you want to to test the real production code, you want to do integration tests, not unit tests. If unit tests is what you want I recommend you to follow my solution proposal, otherwise create integration tests. I don't know how to convince you more then I've tried so read more and from several sources and I think you will agree later on how to do unit testing in this case :)
Good luck!

Why does not specifying view name cause my unit test to fail?

In my unit tests, I find that when I return from a controller action using View() with no view name, ViewResult.ViewName is set to string.Empty. In order for this to be set, it has to be specified as a parameter to the View() call. For example, given the following unit test:
[TextFixture]
public class MyControllerTests
{
[Test]
public void TestMyAction()
{
var controller = new MyController();
var result = controller.MyAction();
Assert.AreEqual("MyAction", result.ViewName);
}
}
The following action implementation will cause the unit test to fail:
public class MyController : Controller
{
public ActionResult MyAction()
{
return View();
}
}
whilst this one will pass:
public class MyController : Controller
{
public ActionResult MyAction()
{
return View("MyAction");
}
}
I'm using ASP.NET MVC 2 (pre-beta) on .NET 4.0. I'm not using anything .NET 4.0-specific, however. I find this behaviour odd because I had thought that the ViewName was one of the reliable properties that could be checked in the unit tests' assertions.
This is a well known "feature" of ASP.NET MVC. Microsoft has documented it since the first version...
When no explicit view name is specified, the framework is trying to find one based on conventions (in "Views\controllername\actionname" or "Shared\controllername\actionname"). ViewName is only relevant if you want to deviate from that convention. So your unit test makes false assumptions.
HTH.

Categories