Introduction
This article explains how to unit test an enumeration which maps a table in the database. The concept is simple: how to ensure an enumeration is up-to-date with a table in the database? And why would you want to do that?
Background
Almost every project has some sort of enumeration that represents the values in a database table. It might be some sort of order status, a category type etc. If an enumeration is present in both code and the database, this makes room for inconsistency. You might update the database and forget to update the code, or do the opposite, and this can introduce problems. It's all about reducing human errors, and making code consistent and easy to maintain. The earlier you are aware of an issue, the better.
Another option is to look into code generation, but that's another article. You can also use this unit test in addition to code generation to make it bullet proof.
How does it work?
I will not delve into the code behind the validation, but I will explain how it works and how you can make use of this feature. Please download the source code if you are interested about what happens behind the scenes.
A custom attribute is put on the enumeration that is to be validated. This attribute describes which table the enumeration corresponds to. A unit test runs the validation, and all enumerations are fetched by using reflection. An enumeration is mapped to a list of fields of type EnumField
, which contains its name and value. E.g. "Winter" and "1". This is also done with the table in the database, and then the two lists are compared with each other and any differences will be discovered. See figure below.
Using the code
In order to validate an enum, we can't really hardcode it. The trick is to use an attribute and tag the enumerations.
using EnumValidation;
[ValidateEnum()]
public enum Season
{
Winter = 1,
Spring,
Summer,
Autumn
}
If the table name differs from the enum name, simply supply the table name in the attribute constructor.
[ValidateEnum("SeasonType")]
public enum Season
...
Fort mapping a bitwise enumeration, All
needs to be present in the database in order to make it work.
[Flags]
[ValidateEnum()]
public enum Season
{
Winter = 1,
Spring,
Summer,
Autumn,
All = Winter | Spring | Summer | Autumn
}
In some cases, you can't use the name column in the table. It might be used for a description text for a dropdown list etc. In such a case, you should add an extra column that represents the enum field name, e.g., a new column named "EnumFieldName". To map this in the code, add the column name after the table name, like this:
[ValidateEnum("Season", "EnumFieldName")]
...
So far, so good. This explains how to set an attribute and include the enumeration in the validation process. Now you need a unit test to trigger the validation. The unit test below will start the validation and hook up on the Validated
event. Any validation is reported through this event, whether it succeeded or not. The Validate
method will only return true
if all the validations succeeded. To run a validation you need to supply a connection to the database. You can either pass a SQL connection or a connection string in the constructor to use the built-in connection provider.
[TestMethod]
public void ValidateEnums()
{
EnumValidator validator = new EnumValidator(ConnectionString);
validator.Validated += new
EventHandler<EnumValidationEventArgs>(validator_Validated);
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
bool success = validator.Validate(assemblies);
Assert.IsTrue(success, "One or more validations failed," +
" or an error occured. Please look " +
"at the error log for details.");
}
As you see, we hook up the Validated
event. If a validation fails, you will get an error description of which name or value went wrong. If an exception occurs, maybe because the wrong table name was submitted, an error message accompanied with an exception will give the details about this. I recommend you use some sort of log tool to be noticed about any issues.
void validator_Validated(object sender, EnumValidationEventArgs e)
{
string message = e.Message;
if (!e.Success)
{
Console.WriteLine(message);
if (e.HasException)
{
}
else
{
}
}
}
Let's have a look on some of the properties that can be set on the validation object.
Use the Target
property if you want to control the direction of the validation. In the example below, I have set that the code should be validated up against the database. The default choice is Target.Both
which will run a cross validation, and all errors will be detected.
validator.Target = Target.Code;
Another option is the IgnoreCase
property. This one is, by default, set to false
. If this one is set to true
, comparison of the enum field name will be case insensitive.
validator.IgnoreCase = true;
Getting started
- Download the demo project if you want to play around and test the validation. I have included a test case for both VSTS (Visual Studio Team System) and NUnit. Use the one appropriate. There is also a Season.sql included. Run this in order to create the test table, and fill it with test data. Remember to edit the connection string in the unit test.
- Download the source if you want to modify or compile the validation code. No unit tests included.
To start using this in your own project, copy and reference the DLL in your project. Make a new unit test, or copy the one supplied in the demo project. Attach some sort of log tool to be notified of errors. You might need to reconsider how to fetch the different DLLs. AppDomain.CurrentDomain.GetAssemblies()
will only get the DLLs referenced and used in your test project.
Well, that's it. Download the demo/source. Try it. Give some feedback.
History
- September 12, 2006 - Created article.