C# – How to unit test a model validation attribute

You can unit test a validation attribute by creating an instance of it and then testing the two methods:

  • IsValid()
  • FormatErrorMessage().

In this article, I’ll show examples of unit testing these methods in a custom validation attribute and in a built-in validation attribute (i.e. [Range]).

Unit testing a custom validation attribute

Consider the following custom validation attribute that implements IsValid(). It checks if an 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)

Unit testing the FormatErrorMessage() method

Validation attributes can also have a FormatErrorMessage() for returning error messages. I’ll show two examples of unit testing this method.

Example 1 – Simple error message string

Let’s say your FormatErrorMessasge() method has an interpolated string that shows an error message containing the name of the property with an error, like this:

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)

Example 2 – Complex error message with a format string

Validation attributes can have error messages with format strings. This allows them to display very helpful error messages. It also makes unit testing FormatErrorMessage() a little more complex.

Here’s an example of unit testing FormatErrorMessage() when it has a format string (note: this is testing the built-in [Range] attribute):

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)

Unit testing a built-in validation attribute

You can test built-in validation attributes, such as [Range]. The reason you’d do this is to verify the way you’re specifying the attribute parameter will work 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.