Unit Testing Data Annotations in Blazor Applications

Introduction

Surprisingly, model validation via Data Annotations, which is key to preserving data consistency and integrity, often goes overlooked and under-documented. Despite the scant resources on this topic, I've found through exploration and testing that implementing this kind of validation in Blazor applications is not only highly beneficial but also straightforward. Let's delve into this less charted area of the Blazor testing world and shed light on the ease of implementing model validation using Data Annotations.

Model validation using Data Annotations

Model validation using Data Annotations is fundamental as it ensures data reliability and consistency by enforcing rules on data before it gets stored or processed. It provides a declarative way to express these rules within the model itself, thus promoting code cleanliness, maintainability, and reusability.

Student model

Consider the Student model with Data Annotations for validation:

using System.ComponentModel.DataAnnotations;

namespace Blog.App.Models;

public class Student
{
    [Required(ErrorMessage = "First name is required.")]
    [StringLength(15, ErrorMessage = "First name must not exceed 15 characters.")]
    public string FirstName { get; set; }
}

Now, we have set up our validation rules, but we can not be certain that these rules actually works as they should. Now, how do we ensure that our validation works as expected? The answer is simple: unit testing. For instance, consider the scenario where we want to test if the validation properly handles an empty or too long FirstName. Here are the unit tests using xUnit:

using System.ComponentModel.DataAnnotations;

namespace Blog.Test.Models;

public class StudentValidationTests
{
    [Fact]
    public void ShouldValidateRequiredFirstName()
    {
        // Arrange
        var student = new Student();

        // Act
        var validationResults = new List<ValidationResult>();
        var validationContext = new ValidationContext(student, null, null);
        Validator.TryValidateObject(student, validationContext, validationResults, true);

        // Assert
        Assert.Contains(validationResults, r => r.ErrorMessage.Contains("First name is required."));
    }
}

Now let's break down what's happening here:

Test 1: Required First Name

In the first test, ShouldValidateRequiredFirstName, we are testing whether our model correctly requires a FirstName. We create a Student object without setting a FirstName, then validate it using Validator.TryValidateObject, collecting the results. The test asserts that one of the errors returned should indicate a required FirstName.

Test 2: Maximum Length of First Name

The second test, ShouldValidateMaxLengthFirstName, tests whether the model correctly validates the FirstName length.

[Fact]
public void ShouldValidateMaxLengthFirstName()
{
    // Arrange
    var student = new Student { FirstName = "ThisIsAVeryLongFirstName!!1" };

    // Act
    var validationResults = new List<ValidationResult>();
    var validationContext = new ValidationContext(student, null, null);
    Validator.TryValidateObject(student, validationContext, validationResults, true);

    // Assert
    Assert.Contains(validationResults,
        r => r.ErrorMessage.Contains("First name must not exceed 15 characters."));
}

Similar to the first test, this one creates a Student object, but with a too-long FirstName, then validates it. It asserts that an error is returned that indicates the FirstName is too long. These examples highlight the general process of unit testing model validation in a Blazor application. I advice to adapted these examples to your project and suit your needs and ensure that your models are validated correctly.