ModelState.IsValid always true when testing Controller in Asp.Net MVC Web Api
Asked Answered
E

5

16

I have tried to make this work and made many google/stackoverflow searches with no luck at all.

I have a simple Model:

public class MovieModel
{
    public string Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Name { get; set; }
}

A method in the controller:

// POST: api/Movies
public IHttpActionResult Post([FromBody]MovieModel movieModel)
{
    if (ModelState.IsValid)
    {
        //Code
    }
}

And a test method (is an integration test, but the same would happen in unit tests):

[TestMethod]
public void MoviesController_Post_Without_Name()
{
    // Arrange
    var model = new MovieModel();
    model.Name = "";

    // Act
    var result = controller.Post(model);

    // Assert
    Assert.IsInstanceOfType(result, typeof(InvalidModelStateResult));
    Assert.AreEqual(6, controller.Get().Count());
}

Despite the fact that the model is clearly invalid it always evaluates the IsValid property to true.

I tried many approaches so far without success.

Essentialism answered 1/6, 2016 at 1:27 Comment(0)
I
9

Your solution probably works, but a better way is using ApiController.Validate method.

public void MoviesController_Post_Without_Name()
{
    // Arrange
    var model = new MovieModel();
    model.Name = "";

    // Act
    controller.Validate(model);   //<---- use the built-in method
    var result = controller.Post(model);

    // Assert
    Assert.IsInstanceOfType(result, typeof(InvalidModelStateResult));
    Assert.AreEqual(6, controller.Get().Count());
}
Insectile answered 1/6, 2016 at 3:44 Comment(4)
I tried this .Validate method before, but it doesn't exist. I'm using MVC 5.Deliladelilah
The microsoft reference shows the Validate method, for version 5 of MVC , but it simply don't work, neither Intellisense nor build works.Deliladelilah
@AndréBaptista Why are you talking about MVC? ApiController is a Web API class, and is found in System.Web.Http. Ensure you added the correct NuGet packages to the test project.Headward
Yes, I know it sounds weird, but I checked out everything: - The nuget package (Microsoft.AspNet.WebApi.Core) is installed and is 5.0.0 version, both in Web and Test projects; - The dll System.Web.Http is referenced and is 5.0.0 version on both the web project and the test project; - the using directive is there; - my controller inherits from ApiController; When I click "Go to definition" (F12 key) over ApiController it shows the method list, and there isn't neither Validate nor TryValidate methods.Deliladelilah
E
12

Thanks to this site, I found out the solution:

private void SimulateValidation(object model)
{
    // mimic the behaviour of the model binder which is responsible for Validating the Model
    var validationContext = new ValidationContext(model, null, null);
    var validationResults = new List<ValidationResult>();
    Validator.TryValidateObject(model, validationContext, validationResults, true);
    foreach (var validationResult in validationResults)
    {
        this.controller.ModelState.AddModelError(validationResult.MemberNames.First(), validationResult.ErrorMessage);
    }
}

And including one line in the test method like this:

public void MoviesController_Post_Without_Name()
{
    // Arrange
    var model = new MovieModel();
    model.Name = "";

    // Act
    SimulateValidation(model);
    var result = controller.Post(model);

    // Assert
    Assert.IsInstanceOfType(result, typeof(InvalidModelStateResult));
    Assert.AreEqual(6, controller.Get().Count());
}

Hope that helps someone, it would have saved me some hours hunting the web.

Essentialism answered 1/6, 2016 at 1:27 Comment(2)
Worked for Core 2.0, for nested just do SimulateValidation(model); SimulateValidation(model.child);Barbrabarbuda
Thank you so much @Andre Baptista, this works for meCapel
I
9

Your solution probably works, but a better way is using ApiController.Validate method.

public void MoviesController_Post_Without_Name()
{
    // Arrange
    var model = new MovieModel();
    model.Name = "";

    // Act
    controller.Validate(model);   //<---- use the built-in method
    var result = controller.Post(model);

    // Assert
    Assert.IsInstanceOfType(result, typeof(InvalidModelStateResult));
    Assert.AreEqual(6, controller.Get().Count());
}
Insectile answered 1/6, 2016 at 3:44 Comment(4)
I tried this .Validate method before, but it doesn't exist. I'm using MVC 5.Deliladelilah
The microsoft reference shows the Validate method, for version 5 of MVC , but it simply don't work, neither Intellisense nor build works.Deliladelilah
@AndréBaptista Why are you talking about MVC? ApiController is a Web API class, and is found in System.Web.Http. Ensure you added the correct NuGet packages to the test project.Headward
Yes, I know it sounds weird, but I checked out everything: - The nuget package (Microsoft.AspNet.WebApi.Core) is installed and is 5.0.0 version, both in Web and Test projects; - The dll System.Web.Http is referenced and is 5.0.0 version on both the web project and the test project; - the using directive is there; - my controller inherits from ApiController; When I click "Go to definition" (F12 key) over ApiController it shows the method list, and there isn't neither Validate nor TryValidate methods.Deliladelilah
H
1

On WebAPI 5.2.7:

[TestMethod]
public void MoviesController_Post_Without_Name()()
{
    // Arrange
    var model = new MovieModel();
    model.Name = "";

    controller.Request = new HttpRequestMessage();
    controller.Configuration = new HttpConfiguration();
    controller.Validate(model);

    // Act
    var result = controller.Post(model);

    // Assert
    ...

This article helped me: https://www.c-sharpcorner.com/article/unit-testing-controllers-in-web-api/

Holub answered 4/4, 2019 at 16:21 Comment(0)
B
0

This worked for me:

public MyResultData Post([FromBody] MyQueryData queryData)
{
    if (!this.Request.Properties.ContainsKey("MS_HttpConfiguration")) 
    {
        this.Request.Properties["MS_HttpConfiguration"] = new HttpConfiguration();
    }

    this.Validate(queryData);

    if (ModelState.IsValid)
    {
        DoSomething();
    }
}

Also check out this question: Validate fails in unit tests

Brandtr answered 21/6, 2018 at 0:16 Comment(0)
U
0

Manually add a model error:

// Arrange
controller.ModelState.AddModelError("fakeError", "fakeMessage");

// Act
var result = controller.Post(model);
Unfathomable answered 7/10, 2021 at 2:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.