Marco
Marco

Reputation: 51

Data Annotations on record fails in unittest

Currently we are setting up a new project and like to use the new records introduced in C# 9. We encounter a problem with DataAnnotations inside the record (constructor) not being triggered during the unittest.

Now the DataAnnotation is triggered when calling the Controller, but when i try to simulate this in a unittest (see code below) it will never return any errors.

        //Unit Testing ASP.NET DataAnnotations validation
        //http://stackoverflow.com/questions/2167811/unit-testing-asp-net-dataannotations-validation
        protected static IList<ValidationResult> ValidateModel(object model)
        {
            var validationResults = new List<ValidationResult>();
            var ctx = new ValidationContext(model, null, null);
            Validator.TryValidateObject(model, ctx, validationResults, true);
            return validationResults;
        }

Currently we created a workaround:

    public record FooRecord(string BarProperty)
    {
        [Required]
        public string BarProperty { get; init; } = BarProperty;

    }

But I'm hoping if someone knows why this happens and maybe know how to solve this using the shorthand syntax:

    public record FooRecord([Required] BarProperty){ }

Upvotes: 5

Views: 3325

Answers (3)

Alexis BG
Alexis BG

Reputation: 11

Solution for ASP.NET Core

In tests, you should use ControllerBase.TryValidateModel instead of Validator.TryValidateObject. Additionally, this validation process will be identical to the one used by ASP.NET. Here’s how to do it:

public record FooDto(
    [StringLength(6)]
    string ProductId
);
public class AspnetRecordAttributesValidationTests
{
    private class TestableController : ControllerBase;

    private static TestableController CreateController()
    {
        // Create a service provider with the required ASP.NET services
        var services = new ServiceCollection();
        services.AddControllers();
        var provider = services.BuildServiceProvider();

        return new TestableController
        {
            // Set up the controller with the model validation service
            ObjectValidator = provider.GetRequiredService<IObjectModelValidator>()
        };
    }

    [Fact]
    public void ProductId_ValidLength_Passes()
    {
        // Validate the record with ASP.NET model validation
        var isValid = CreateController().TryValidateModel(new FooDto("123456"));

        Assert.True(isValid);
    }

    [Fact]
    public void ProductId_InvalidLength_Fails()
    {
        var controller = CreateController();

        var isValid = controller.TryValidateModel(new FooDto("123456789"));

        Assert.False(isValid);
        Assert.Equal(1, controller.ModelState.ErrorCount);
    }
}

Upvotes: 1

KUTlime
KUTlime

Reputation: 8007

Behavior of ASP.NET Core request parameters

You can't apply validation attributes [property: ... ] to your request parameters that undergoes binding & validation, see this question.

You will receive InvalidOperationException(...) like @SerjG describes in his comment.

It happend to me too and that is the reason why end up in this SO thread.

For details, see this issue.

Solution

  • Replace remove your validation attributes.
  • Use Fluent Validation instead.

Upvotes: 0

zeroid
zeroid

Reputation: 731

It will work as expected if you define your record as:

public record FooRecord([property: Required] BarProperty){ }

Upvotes: 6

Related Questions