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.
Table of Contents
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.