Testing Your Unity XML Configuration

When using Unity as your IoC container, one way to configure the container is to use an configuration section in your app.config or web.config file.  A simplified example of this configuration is shown below:

    1: <?xml version="1.0" encoding="utf-8" ?>
    2: <configuration>
    3:   <configSections>
    4:     <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration" />
    5:   </configSections>
    6:  
    7:   <unity>
    8:     <typeAliases>
    9:       <typeAlias alias="IMyInterface1" 
   10:                  type="UnityConfigTestApp.IMyInterface1, UnityConfigTestApp" />
   11:       <typeAlias alias="MyClass1" 
   12:                  type="UnityConfigTestApp.MyClass1, UnityConfigTestApp" />
   13:     </typeAliases>
   14:     <containers>
   15:       <container>
   16:         <types>
   17:           <type type="IMyInterface1" mapTo="MyClass1" />
   18:         </types>
   19:       </container>
   20:     </containers>
   21:   </unity>
   22: </configuration>

This configuration allows you to specify the types and the mappings for your Unity container.  An example of how to implement this concept can be found here: https://weblogs.asp.net/podwysocki/archive/2008/03/27/ioc-and-unity-configuration-changes-for-the-better.aspx

In this post, I am going to show an easy way to test the validity of your Unity XML configuration.  In the example configuration above, we are only dealing with two types and one mapping so things are not too complicated.  But in a real application you might have a large number of types and mappings which can make the configuration difficult to maintain.  A small typo can cause your application to fail at runtime since there is no compile time validation of the Unity configuration.

To perform this validation, I am going to write a couple of simple unit tests.  Now, these are not true unit tests since they are not testing a unit of code, but I found a unit test construct to be the best way to perform this validation in a simple and quickly testable way.  The two unit tests that I wrote are shown below:

    1: using System;
    2: using System.Linq;
    3: using System.Text.RegularExpressions;
    4: using System.Xml;
    5: using Microsoft.VisualStudio.TestTools.UnitTesting;
    6:  
    7: [TestMethod]
    8: [DeploymentItem(@"UnityConfigTestApp\App.config")]
    9: public void TypeAliases_should_reference_valid_types()
   10: {
   11:     XmlDocument unityConfig = new XmlDocument();
   12:     unityConfig.Load("App.config");
   13:     XmlNode unityNode = unityConfig.DocumentElement.SelectSingleNode("unity");
   14:     XmlNodeList typeAliases = unityNode.SelectNodes("typeAliases/typeAlias");
   15:  
   16:     foreach (XmlNode typeAliasNode in typeAliases)
   17:     {
   18:         string typeFullName = RemoveWhiteSpace(typeAliasNode.Attributes["type"].Value);
   19:         Type currentType = Type.GetType(typeFullName);
   20:  
   21:         Assert.IsNotNull(currentType, String.Format("'{0}' is not a valid type.", typeFullName));
   22:     }
   23: }
   24:  
   25: [TestMethod]
   26: [DeploymentItem(@"UnityConfigTestApp\App.config")]
   27: public void Type_mappings_should_be_compatible()
   28: {
   29:     XmlDocument unityConfig = new XmlDocument();
   30:     unityConfig.Load("App.config");
   31:     XmlNode unityNode = unityConfig.DocumentElement.SelectSingleNode("unity");
   32:     XmlNodeList typeList = unityNode.SelectNodes("containers/container/types/type");
   33:  
   34:     foreach (XmlNode typeNode in typeList)
   35:     {
   36:         string typeName = typeNode.Attributes["type"].Value;
   37:         string mapToName = typeNode.Attributes["mapTo"].Value;
   38:  
   39:         XmlNode typeTypeAliasNode = unityNode.SelectSingleNode(String.Format("typeAliases/typeAlias[@alias = \"{0}\"]", typeName));
   40:         XmlNode mapToTypeAliasNode = unityNode.SelectSingleNode(String.Format("typeAliases/typeAlias[@alias = \"{0}\"]", mapToName));
   41:  
   42:         Assert.IsNotNull(typeTypeAliasNode, String.Format("Could not locate a typeAlias element for '{0}'.", typeName));
   43:         Assert.IsNotNull(mapToTypeAliasNode, String.Format("Could not locate a typeAlias element for '{0}'.", mapToName));
   44:  
   45:         string typeFullName = RemoveWhiteSpace(typeTypeAliasNode.Attributes["type"].Value);
   46:         string mapToFullName = RemoveWhiteSpace(mapToTypeAliasNode.Attributes["type"].Value);
   47:  
   48:         Type currentType = Type.GetType(typeFullName);
   49:         Type mapToType = Type.GetType(mapToFullName);
   50:         Assert.IsNotNull(currentType, String.Format("'{0}' is not a valid type.", typeFullName));
   51:         Assert.IsNotNull(mapToType, String.Format("'{0}' is not a valid type.", mapToFullName));
   52:  
   53:         bool areTypesCompatible = false;
   54:  
   55:         if (currentType.IsAssignableFrom(mapToType) ||                                          // test assignabilty
   56:             mapToType.Name == currentType.Name ||                                               // or matching names
   57:             mapToType.GetInterfaces().Where(x => x.Name == currentType.Name).Count() == 1 ||    // or generic interfaces
   58:             (mapToType.BaseType != null && mapToType.BaseType.Name == currentType.Name))        // or generic base classes
   59:         {
   60:             areTypesCompatible = true;
   61:         }
   62:  
   63:         Assert.IsTrue(areTypesCompatible, String.Format("Cannot map '{0}' to '{1}'.", typeName, mapToName));                
   64:     }
   65: }
   66:  
   67: private static string RemoveWhiteSpace(string inputString)
   68: {
   69:     if (inputString == null)
   70:         return null;
   71:  
   72:     return Regex.Replace(inputString, @"[\s\r\n]", String.Empty);
   73: }

A couple of things about configuring the tests:

  1. You will notice the DeploymentItemAttribute on each of the tests.  This references the relative path to the config file from the solution directory.
  2. Any assemblies that are referenced in the unity configuration section need to be included as references in the test project.

The first test validates that the types specified in the <typeAliases> are valid types.  We just iterate through each <typeAlias> and check that the type can be found.  If it can be found, then it should be a valid type.

The second tests validates the type mapping specified in the <type> elements of the configuration.  To do this, we get the full names for the types by referencing back to the matching <typeAlias> elements.  We then get a reference to those types and compare them for compatibility (This part is done at line 55 in the code above).  The types are checked for compatibility by following a set of rules:

  1. Check to see if the types are directly assignable.
  2. Check to see if the names match.
  3. Check to see if the to type implements the from interface (needed for generic interfaces)
  4. Check to see if the to type inherits from the from base class (needed for generic base classes)

Once we have these tests in our test suite, we can validate the types and mappings without having to run the application and hope that we didn’t mistype something in the configuration.