-1

I would like to test my controller method for a given MyModelDTO values.

This is my controller Post method (simplified):

[HttpPost]
public ActionResult Post([FromBody] MyModelDTO itemDTO)
{
    ModelState.Remove($"{nameof(itemDTO)}.{nameof(itemDTO.Id)}");
    if (!ModelState.IsValid)
    {
        return BadRequest();
    }
    //rest of code
}

My MyModelDTO class:

public class MyModelDTO
{
    [IsNotEmpty(ErrorMessage = "Guid Id Is Empty")]
    public Guid Id { get; set; }
}

My custom ValidationAttribute:

public class IsNotEmptyAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        if (value == null) return false;

        var valueType = value.GetType();
        var emptyField = valueType.GetField("Empty");

        if (emptyField == null) return true;

        var emptyValue = emptyField.GetValue(null);

        return !value.Equals(emptyValue);
    }
}

My question is how to test the automatic validation for the ModelState's custom attribute?

This is what I've tried:

[Test] public void Post_WhenCalled_ShouldReturnPostResult()
{
    using (var mock = AutoMock.GetLoose())
    {
        //Arrange
        var controller = mock.Create<MyController>();

        //Act
        ActionResult actionResult = controller.Post(new MyModelDTO());

        //Assert... 
     }
}

The unit test works OK (the controller should work with a parameter MyModelDTO with no Id), but it looks like it does not really mocking the automatic validation process of ModelState. How do I know this? because when I try to do a postman with body missing of Id property it result with "Guid Id Is Empty" message. it won't even stop at the breakpoint.

Shahar Shokrani
  • 7,598
  • 9
  • 48
  • 91
  • Can't you use the required attribute? – Avin Kavish Jun 17 '19 at 12:30
  • Also if you are unit testing, all you need to do is call `IsValid` with different objects, you don't need to go through the controller and model binder. Those two are already tested by the upstream project. – Avin Kavish Jun 17 '19 at 12:37
  • Then you test that `IsNotEmptyAttribute` behaves as expected when given a `MyModelDTO` – Nkosi Jun 17 '19 at 12:54
  • 1
    You are trying to test framework concerns in a unit test. – Nkosi Jun 17 '19 at 12:55
  • Hey @AvinKavish, I think require won't help if the Guid is an empty guid – Shahar Shokrani Jun 17 '19 at 14:22
  • You logic is a bit fudged. All you need to do if you are checking for a guid that is not empty is, `return value is Guid guid && guid != Guid.Empty` there is no need to use reflection to lookup the static value. Just access it. As for casting to Guid, use a pattern matching assignment that short circuits. – Avin Kavish Jun 17 '19 at 14:49
  • I want that the entire validation will work only with model state, no additional if-s – Shahar Shokrani Jun 17 '19 at 14:54

2 Answers2

2

You have two choice: you can unit test it or you can integration test it. The problem now is that you're attempting to mix the two approaches.

To do a proper unit test, you'd just instantiate the attribute itself and pass data to IsValid to ensure that the it returns true/false correctly. There is no need for a controller or anything else. You're essentially just testing the actual method that does the validation to ensure that it performs correctly.

If you want to fully test it, to ensure it works in an actual situation of being pass data from a request inside the pipeline, then you need to do an integration test, but that requires the user of the test server and actually making real request using the test client (which is just an HttpClient instance.

Just newing up a controller and calling an action like a method is not enough. Amongst other differences, it doesn't involve the modelbinder, which also therefore does not involve any of the validation machinery. In a word, your attribute isn't working because it's never even invoked.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
2

Model validation, as part of the model binding process, gets invoked outside of the controller. That means that you cannot really test it inside of a controller unit test. Instead, when testing the controller, you will basically already have to mock the model state if you want to verify your controller’s behavior based on the model state.

There are basically two things you can do here: If you just want to test your validation logic, then the best way to do this is to simply invoke the ValidationAttribute directly. So you don’t test the controller but you test your attribute.

You can simply instantiate your attribute, and then run the Validate method to test its behavior. Just pass an instance of the object you want to validate, and you can verify the exceptions it throws.

The alternative solution would be to do a full integration test. That way, you don’t unit test your controller but instead you test the whole request pipeline, including the controller and the model validation. For specific scenarios, this is the best way to make sure that everything works end-to-end.

poke
  • 369,085
  • 72
  • 557
  • 602