ASP.NET Core – How to unit test a model validation attribute

You can directly unit test a model validation attribute by creating an instance of the attribute class, and then testing the two primary methods: IsValid() and FormatErrorMessage().

Here’s an example of unit testing a custom validation attribute:

[TestMethod()] public void IsValidTest_WhenEven_ReturnsTrue() { //arrange var evenIntegerAttribute = new EvenIntegerAttribute(); int input = 2; //act var isValid = evenIntegerAttribute.IsValid(input); //assert Assert.IsTrue(isValid); }
Code language: C# (cs)

You can also test the built-in validation attributes (such as [Range], [RegularExpression], etc..), which is useful when you want to test them with specific parameters you’re really using in your models.

In this article, I’ll show more examples of unit testing model validation attributes.

Testing IsValid()

Custom validation attribute

Consider the following custom validation attribute that checks if the input is an even integer:

using System.ComponentModel.DataAnnotations; public class EvenIntegerAttribute : ValidationAttribute { public override bool IsValid(object value) { if (value is int number && number % 2 == 0) return true; return false; } }
Code language: C# (cs)

There are four test cases here:

  • When the input is null, return false. Note: The ‘is’ operator returns false when used on a null.
  • When the input is not the expected type (int), return false.
  • When the input is an odd integer, return false.
  • When the input is an even integer, return true.

Here’s the parameterized unit test for these test cases:

[DataRow(null, false)] [DataRow("0", false)] [DataRow(1, false)] [DataRow(2, true)] [TestMethod()] public void IsValidTest(object input, bool expectedIsValidResult) { //arrange var evenIntegerAttribute = new EvenIntegerAttribute(); //act var actualIsValidResult = evenIntegerAttribute.IsValid(input); //assert Assert.AreEqual(expectedIsValidResult, actualIsValidResult); }
Code language: C# (cs)

Built-in validation attribute

The main reason to test a built-in validation attribute is so you can verify your specific parameters are handled by the attribute as expected. Consider the following code that uses the [Range] attribute to validate that the input is within a given date range:

using System.ComponentModel.DataAnnotations; [TestMethod] public void TestRange_WhenDateWithinRange_ReturnsTrue() { //act var rangeValidation = new RangeAttribute(typeof(DateTime), minimum: "2022-05-01", maximum: "2022-05-31"); DateTime input = new DateTime(year: 2022, month: 5, day: 22); //arrange var isValid = rangeValidation.IsValid(input); //assert Assert.IsTrue(isValid); }
Code language: C# (cs)

Note: You have to add reference to System.ComponentModel.DataAnnotations to be able to test the built-in attributes.

Testing FormatErrorMessage()

Simple – hardcoded error message

Let’s say you have the following FormatErrorMessage() implementation in your custom validation attribute:

public override string FormatErrorMessage(string name) { return $"{name} must be an even integer"; }
Code language: C# (cs)

Here’s a unit test that verifies FormatErrorMessage() uses the name parameter in a hardcoded error message:

[TestMethod()] public void FormatErrorMessageTest_HasPropertyNameAndSpecificErrorMessage() { //arrange var evenIntegerAttribute = new EvenIntegerAttribute(); string name = "Test"; string expected = "Test must be an even integer"; //act var errorMessage = evenIntegerAttribute.FormatErrorMessage(name); //assert Assert.AreEqual(expected, errorMessage); }
Code language: C# (cs)

Complex – using a format string with ErrorMessage

The ValidationAttribute class was designed to handle complex error message formatting scenarios. Here’s an example of unit testing FormatErrorMessage() when it’s using a format string provided by the ErrorMessage property:

using System.ComponentModel.DataAnnotations; [TestMethod] public void TestRange_ErrorMessageUsesExpectedFormat() { //act var rangeValidation = new RangeAttribute(minimum: 0, maximum: 10); rangeValidation.ErrorMessage = "{0} is out of range ({1}-{2})"; string expected = "Test is out of range (0-10)"; //arrange var formattedErrorMessage = rangeValidation.FormatErrorMessage("Test"); //assert Assert.AreEqual(expected, formattedErrorMessage); }
Code language: C# (cs)

This is the equivalent of using the [Range] attribute like this:

[Range(minimum: 0, maximum: 10, ErrorMessage = "{0} is out of range ({1}-{2})")] public int Seats { get; set; }
Code language: C# (cs)

Note: You’d probably want to put the format string in a constant accessible to the code using the attribute and the test.

Leave a Comment