Geeks With Blogs

News Stack Overflow profile for Thomas Weller
Thomas Weller on bitbucket.org
Gallio Banner

Mercurial

Typemock Isolator

Can’t code without   The best C# coding assistance and
    refactoring plugin for Visual Studio


// ThomasWeller C#/.NET software development, software integrity, life as a freelancer, and all the rest

In some scenarios, you may have a database that contains text data in a column which is restricted to a certain set of discrete values. In such a case it is a good idea to use an enumeration for representing these data in your domain. - I occasionally came across this situation when I had to deal with legacy databases with large amounts of pre-existing data. - This post demonstrates an easy and effective way to put this "enum value vs. database string" mapping under test, using the xUnit.net unit testing framework and a bit of NHibernate along the way.

Let's look at an example from Microsoft's AdventureWorks sample database (namely the LT version). Among other things, there are customers and adresses, related to each other via a reference table that establishes a many-to-many relationship between the two (a customer can have many addresses):

Of course a customer cannot have an arbitrary bunch of addresses, but the kinds of addresses for a customer are restricted by the AddressType column of the CustomerAddress table. The description for this column states the following:

"The kind of Address. One of: Archive, Billing, Home, Main Office, Primary, Shipping"

I frequently saw such things represented with pure string values ("magic strings") in the business domain, leading to code like this: 

address.addresstype = "shipping";

This is ugly and bad practice at the least. In the worst case, such code can be the root of massive maintenance nightmares. It's much more preferable to have the address types represented by some constant values, for example with an enum like this:

namespace EnumsAndDatabase

{

    /// <summary>Defines various types for addresses.</summary>

    public enum AddressType

    {

        /// <summary>Undefined.</summary>

        Undefined = 0,

 

        /// <summary>Archived address.</summary>

        Archive,

 

        /// <summary>Billing address.</summary>

        Billing,

 

        /// <summary>Home address.</summary>

        Home,

 

        /// <summary>Main Office address.</summary>

        MainOffice,

 

        /// <summary>Primary address.</summary>

        Primary,

 

        /// <summary>Shipping address.</summary>

        Shipping

 

    } // enum AddressType

 

} // namespace EnumsAndDatabase

With that you could write code like the below, which is much safer and better in many respects (it's typesafe and can easily be refactored, IntelliSense will save you some keystrokes and prevent you from introducing typos...):

address.addresstype = Addresstype.Shipping;

In a previous post, I demonstrated how you can easily put such an enum<->database value relation under test to make sure that the database content is always in sync with its code representation (the enum).

But there's a small problem here: The methodology in this post silently made the assumption, that there's an exact mapping between the database value and the string representation of the corresponding enum member. In the above example, this is not the case. Rather you have the string "Main Office" in your database mapped to the enum member MainOffice, which for obvious reasons has no blank between the two words. So what we need here is a mechanism that binds an arbitrary string to a corresponding enum value. This is where NHibernate comes to the rescue.

(Remark: I assume a scenario where your business logic has a bearable complex domain model and you use NH to map it to the database. If you don't have NH already in place, then the described enum mapping problem is certainly not a sufficient reason to add it to your project. You might be better off with some self-written mapping mechanism or with some simple string constants in such a case.)

NH, amongst many many other things, contains the concept of custom types. Basically this allows you to define a mapping between anything in your code and anything else in the underlying database (By implementing the IUserType interface or one of its derivations. This is one of the many extension points for which I love NH so much). And since the enum<->string mapping scenario is quite common, NH 2.1 provides a predefined base class for such a mapping: EnumStringType<T>.

So this is how we define the mapping between our AddressType enumeration and the CustomerAddress.AddressType database column with this type:

using System;

using NHibernate.Type;

 

namespace EnumsAndDatabase

{

    /// <summary>

    /// Custom NH datatype to map the <see cref="AddressType"/> enum

    /// to db column <c>CustomerAddress.AddressType</c>

    /// </summary>

    public class AddressTypeEnumStringType : EnumStringType<AddressType>

    {

        #region Overrides

 

        /// <summary>

        /// Gets the string value for an enum value of type <see cref="AddressType"/>.

        /// </summary>

        /// <param name="enumValue">The enum value.</param>

        /// <returns>The corresponding string value.</returns>

        public override object GetValue(object enumValue)

        {

            if (enumValue == null)

            {

                throw new ArgumentNullException("enumValue");

            }

 

            return (AddressType)enumValue == AddressType.MainOffice

                                    ? "Main Office"

                                    : base.GetValue(enumValue);

        }

 

        /// <summary>

        /// Gets the enum value of type <see cref="AddressType"/> for a string.

        /// </summary>

        /// <param name="value">The string value.</param>

        /// <returns>The corresponding enum value.</returns>

        public override object GetInstance(object value)

        {

            if (value == null)

            {

                throw new ArgumentNullException("value");

            }

 

            return (value.ToString().ToLower() == "main office")

                            ? AddressType.MainOffice

                            : base.GetInstance(value);

        }

 

        #endregion // Overrides

 

    } // class AddressTypeEnumStringType

 

} // namespace EnumsAndDatabase

 What this small class does, is essentially nothing but intercepting the traffic between the database and the application and converting the respective values back and forth as required.

Ok, with this little helper now in place, a test (using xUnit.net) to verify that the CustomerAddress.AddressType column contains only values that are valid members of the AddressType enum, looks like this:

using Xunit.Extensions;

 

namespace EnumsAndDatabase

{

    public class AddressTypeFixture : EnumStringTypeValidationBase<AddressType>

    {

        public AddressTypeFixture() : base(new AddressTypeEnumStringType())

        {

        }

 

        [Theory]

        [SqlServerData("DB", "AdventureWorksLT2008", "SELECT DISTINCT AddressType FROM SalesLT.CustomerAddress")]

        public void ValidateEnumAgainstDatabase(string name)

        {

            base.ValidateEnumValue(name);

        }

 

    } // class AddressTypeFixture

 

} // namespace EnumsAndDatabase

The base class of our test fixture (again: I know, that "test fixture" is not really xUnit.net-like) takes the custom user type as a constructor argument and uses it to validate the mapping. You then declare the SQL statement to retrieve the string values in question and that's it. - Oh, and don't forget the DISTINCT in your SQL statement (as I initially did...), or you likely will experience hefty performance problems and have miriads of useless test log entries ;-).

Here are the base classes that I usually use for this, namely EnumStringTypeValidationBase<T> and its base EnumValidationBase<T>:

namespace EnumsAndDatabase

{

    /// <summary>

    /// Provides a base class for a (xUnit.net) test harness that validates an

    /// <see cref="Enum"/> against a set of custom strings, using NHibernate's

    /// <see cref="EnumStringType{T}"/>.

    /// </summary>

    /// <typeparam name="TEnum">The type of the enum under test.</typeparam>

    /// <example>

    /// The below sample demonstrates tha validatation of the enum type <c>AddressType</c>

    /// against a corresponding database column:

    /// <code>

    ///     public class AddressTypeFixture : EnumStringTypeValidationBase&lt;AddressType&gt;

    ///     {

    ///         public AddressTypeFixture() : base(new AddressTypeEnumStringType())

    ///         {

    ///         }

 

    ///         [Theory]

    ///         [SqlServerData("DB", "AdventureWorksLT2008", "SELECT DISTINCT AddressType FROM SalesLT.CustomerAddress")]

    ///         public void ValidateEnumAgainstDatabase(string name)

    ///         {

    ///             base.ValidateEnumValue(name);

    ///         }

    ///

    ///     } // class AddressTypeFixture

    /// </code>

    /// </example>

    public abstract class EnumStringTypeValidationBase<TEnum> : EnumValidationBase<TEnum>

        where TEnum : struct

    {

        #region Fields

 

        private readonly EnumStringType<TEnum> mapper;

 

        #endregion // Fields

 

        #region Properties

 

        protected EnumStringType<TEnum> EnumStringMapper

        {

            get { return this.mapper; }

        }

 

        #endregion // Properties

 

        #region Construction

 

        protected EnumStringTypeValidationBase(EnumStringType<TEnum> mapper)

        {

            this.mapper = mapper;

        }

 

        protected EnumStringTypeValidationBase() : this(null)

        {

        }

 

        #endregion // Construction

 

        #region Implementation

 

        protected override void ValidateEnumValue(string valueName, object expectedNumericalValue, bool ignoreCase)

        {

            string name = (this.EnumStringMapper != null)

                                ? this.EnumStringMapper.GetInstance(valueName).ToString()

                                : valueName;

 

            base.ValidateEnumValue(name, expectedNumericalValue, ignoreCase);

        }

 

        #endregion // Implementation

 

    } // class EnumStringTypeValidationBase

 

 

    /// <summary>

    /// Provides some predefined useful methods for derived test classes (based on

    /// xUnit.net) for validating enums against corresponding database tables

    /// (i.e. the tests should make sure there is a 1:1 match).

    /// </summary>

    /// <typeparam name="TEnum">The enum.</typeparam>

    public abstract class EnumValidationBase<TEnum> where TEnum : struct

    {

        /// <summary>

        /// Validates that a given number is equal to the number of values in the specified enum.

        /// </summary>

        /// <typeparam name="TEnum">The enum.</typeparam>

        /// <param name="expectedNumberOfValues">

        /// The expected number of values for the enum of type <typeparamref name="TEnum"/>.

        /// </param>

        protected void ValidateNumberOfValues(int expectedNumberOfValues)

        {

            int numberOfValuesAsDefinedByEnum = Enum.GetValues(typeof(TEnum)).Length;

 

            Assert.Equal(expectedNumberOfValues, numberOfValuesAsDefinedByEnum);

        }

 

        /// <summary>

        /// Validates that a given name represents a member of the specified enum

        /// and that it has the specified value.

        /// </summary>

        /// <param name="valueName">The name of an enum value.</param>

        /// <param name="expectedNumericalValue">

        /// The (optional) numeric value that the enum member must have.

        /// If this value is <see langword="null"/>, no validation occurs.

        /// </param>

        /// <param name="ignoreCase">

        /// If set to <see langword="true"/>, a case insensitive search

        ///  for the corresponding enum member is made.

        /// </param>

        protected virtual void ValidateEnumValue(string valueName, object expectedNumericalValue, bool ignoreCase)

        {

            Type enumType = typeof(TEnum);

            object val = null;

 

            // assert that the specified string is actually convertible to the given enum

            Assert.DoesNotThrow(() => val = Enum.Parse(enumType, valueName, ignoreCase));

 

            // assert that the specified numerical value is actually correct

            if (expectedNumericalValue != null)

            {

                Assert.Equal(val, Enum.ToObject(enumType, expectedNumericalValue));

            }

        }

 

        /// <summary>

        /// Validates that a given name represents a member of the specified enum (case

        /// sensitive) and that it has the specified value.

        /// </summary>

        /// <param name="valueName">The name of an enum value.</param>

        /// <param name="expectedNumericalValue">

        /// The (optional) numeric value that the enum member must have.

        /// If this value is <see langword="null"/>, no validation occurs.

        /// </param>

        protected void ValidateEnumValue(string valueName, object expectedNumericalValue)

        {

            this.ValidateEnumValue(valueName, expectedNumericalValue, false);

        }

 

        /// <summary>

        /// Validates that a given name represents a member of the specified enum.

        /// </summary>

        /// <param name="valueName">The name of an enum value.</param>

        protected void ValidateEnumValue(string valueName)

        {

            this.ValidateEnumValue(valueName, null, false);

        }

 

    } // class EnumValidationBase

 

} // namespace EnumsAndDatabase

The EnumStringTypeValidationBase<T> class can also work without a custom user type. It then simply takes the pure string representations of the respective enum.

When running the above test in ReSharper with the Gallio Test runner, you will get this:

 Nice, and all you ever need to know...

 

I prepared a small sample solution (VS 2008) that contains the two base classes for the unit test classes together with the here outlined usage examples. To make the tests pass and to play with the code you need of course the AdventureWorksLT database installed on a MS SQL Server instance (you can get it here), and you also need a database table called CATEGORIES, which you can easily figure out from its picture in my previous post. The referenced NHibernate and xUnit.net assemblies come with the download, so there's no external dependency except the database.

 

Posted on Tuesday, August 25, 2009 4:01 AM Unit Testing/TDD , Readability , Automation , NHibernate | Back to top


Comments on this post: Keeping your enums in sync with database strings

No comments posted yet.
Your comment:
 (will show your gravatar)


Copyright © Thomas Weller | Powered by: GeeksWithBlogs.net