Click here to Skip to main content
15,901,368 members
Articles / Programming Languages / C#

Unit Testing your App.config and ConfigurationSection using NUnit, Ninject and TestDriven.NET

Rate me:
Please Sign up or sign in to vote.
4.81/5 (9 votes)
10 Apr 2010Ms-PL4 min read 59.3K   416   32   1
The title says it all...

Introduction

Let me just make things clear. This post is not about creating mocks.
This post is about really unit testing the different scenarios you have when working with configuration files:

  • What if the section is missing?
  • What if required fields are missing?
  • What values can I expect when optional fields are missing?
  • ...

To get started, you need the following:

NUnit is the testing framework we'll use to perform our unit tests.
Ninject will be for dependency injection.
TestDriven.NET will be used to run the tests.

And I presume you have basic knowledge of unit testing and dependency injection.

Preparing the Application

Ok, first, we'll create a command line application that reads the App.config and displays the contents.

cmdlineapp.png

In brief, this is how the application will work:

configdiag.png

  1. There will be a ConfigurationSection mapping the fields with the App.config (ServerConfigurationSection)
  2. This section implements the IServerConfiguration interface
  3. Then we'll have the ServerConfigurationProvider, this class is responsible for reading the configuration file and throwing exceptions if something goes wrong.
  4. The provider implements IServerConfigurationProvider that will be registered in our DI container.

Creating the ServerConfigurationSection

So first, we'll create the ServerConfigurationSection responsible for mapping the fields:

C#
public class ServerConfigurationSection : ConfigurationSection, IServerConfiguration
{
   [ConfigurationProperty("server", IsRequired = true)]
   public string Server
   {
       get { return (string)this["server"]; }
       set { this["server"] = value; }
   }

   [ConfigurationProperty("port", IsRequired = false)]
   public int Port
   {
       get { return (int)this["port"]; }
       set { this["port"] = value; }
   }
}

public interface IServerConfiguration
{
    int Port { get; set; }
    string Server { get; set; }
}

As you can see we have 2 fields, Server and Port. Port is an optional field.

Now that we have this section, we can create the following App.config file:

XML
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="srvConfig"
      type="Sandrino.MyApplication.ServerConfigurationSection, Sandrino.MyApplication"/>
  </configSections>
  <srvConfig server="sandrino.loc" port="1986"/>
</configuration>

Creating the ServerConfigurationProvider

Now that we have a ConfigurationSection, we can go ahead and create the provider.
Our provider will be read the App.config using the mapping available in the configuration section.

This is the base class that can be reused to quickly create new providers:

C#
public abstract class ConfigurationProviderBase<TSection, TException>
   where TSection : ConfigurationSection
   where TException : Exception, new()
{
   /// <summary>
   /// Name of the section to read from the app.config.
   /// </summary>
   private string sectionName;

   /// <summary>
   /// Config object used to read a custom configuration file.
   /// If not set the default file will be used.
   /// </summary>
   private System.Configuration.Configuration config;

   /// <summary>
   /// Initialize the provider.
   /// </summary>
   /// <param name="sectionName"></param>
   public ConfigurationProviderBase(string sectionName)
   {
       this.sectionName = sectionName;
   }

   /// <summary>
   /// Set a custom configuration file.
   /// </summary>
   /// <param name="config"></param>
   public void SetConfigurationFile(string file)
   {
       // Create the mapping.
       ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
       fileMap.ExeConfigFilename = file;

       // Open the configuration.
       config = ConfigurationManager.OpenMappedExeConfiguration
       (fileMap, ConfigurationUserLevel.None);
   }

   /// <summary>
   /// Read the configuration file.
   /// </summary>
   /// <returns></returns>
   protected TSection Read()
   {
       // Try to read the config section.
       TSection section = GetSection() as TSection;
       if (section == null)
           throw new TException();

       // Done.
       return section;
   }

   /// <summary>
   /// Get the section from the default configuration file or from the custom one.
   /// </summary>
   /// <returns></returns>
   private object GetSection()
   {
       if (config != null)
           return config.GetSection(sectionName);
       else
           return ConfigurationManager.GetSection(sectionName);
   }
}

What this class does in a few words:

  • It accepts a section name to read. This is how you'll call the ConfigurationSection in your App.config.
    In our example, this is srvConfig.
  • Accepts a custom configuration file (using SetConfigurationFile)
  • Can read a configuration section from the App.config or will throw an exception if the section is not found

Now to use it for our ServerConfigurationSection, we'll create the ServerConfigurationProvider:

C#
public class ServerConfigurationProvider :
   ConfigurationProviderBase<ServerConfigurationSection,
   ServerConfigurationMissingException>, IServerConfigurationProvider
{
     public ServerConfigurationProvider()
         : base("srvConfig")
     {

     }

     /// <summary>
     /// Read the configuration and just return the interface.
     /// </summary>
     /// <returns></returns>
     public new IServerConfiguration Read()
     {
         return base.Read();
     }
}

public interface IServerConfigurationProvider
{
   IServerConfiguration Read();
}

As you can see, we just inherit from the ConfigurationProviderBase and just return the interface IServerConfiguration instead of the ServerConfigurationSection.
Why? I'm not going to go into "What is a loosly coupled architecture", but if we do it this way the reference to System.Configuration (and the assembly) stays minimal.

Creating the Application

First we'll need to make sure our application knows that IServerConfigurationProvider == ServerConfigurationProvider.
Later on, we'll see why we also abstract the provider to an interface.

First, we'll use Ninject to bind the provider to its interface. Any other dependency injector will also do.

C#
public class ConfigurationModule : StandardModule
{
   public override void Load()
   {
       Bind<IServerConfigurationProvider>().To<ServerConfigurationProvider>();
   }
}

Finally, we can go ahead and create our command line application:

C#
class Program
{
   static void Main(string[] args)
   {
       using (IKernel krn = new StandardKernel())
       {
           // Create bindings.
           krn.Load(new ConfigurationModule());

           // Get the provider and read the app.config.
           var prov = krn.Get<IServerConfigurationProvider>();
           var config = prov.Read();

           // Display.
           Console.WriteLine("Server: {0}", config.Server);
           Console.WriteLine("Port: {0}", config.Port);
           Console.Read();
       }
   }
}

Again, if you can't read the comments:

  • First we create a new kernel (or you might call it a container) and load the ConfigurationModule. This will create the bindings for the provider.
  • Then using Get on the kernel, we'll get an instance of the provider and we read the configuration file.
  • And finally we display the contents.

And shazooom... it works.

Testing the Configuration

This is where it gets interesting. Now we'll need to test the possible outcomes when reading the configuration file.

  • What if the section is missing?
  • What if required fields are missing?
  • What values can I expect when optional fields are missing?
  • ...

Setting up the Test Project

First of all, we'll need to create a new project of type class library. We also add a reference to Ninject, NUnit, our main project.

And finally, we also create multiple configuration files. Each configuration file will have something different (a value missing, the section missing, ...) to cover each possible scenario.

testsetup.png

Now, before writing the actual tests, remember I said that we'll see later why ServerConfigurationProvider was also abstracted to the interface IServerConfigurationProvider?

Because, using Ninject we can do some pretty cool stuff. Look at this:

C#
private void RegisterConfiguration(string filename)
{
   krn = new StandardKernel(new InlineModule(m => m
       .Bind<IServerConfigurationProvider>()
       .ToMethod<IServerConfigurationProvider>(
           (e) =>
           {
               ServerConfigurationProvider config = new ServerConfigurationProvider();
               config.SetConfigurationFile(filename);
               return config;
           })
       )
   );
}

In English, this would be:

  • Bind the interface IServerConfigurationProvider to a method
  • This method should return an IServerConfigurationProvider
  • We declare the method directly (inline) using a lambda expression

And in the method we declare, we just create a new configuration provider and pass it our custom filename.
But the actual tests and event the classes that could use the provider are not aware of this.

The great thing is, maybe next year you'll move to a registry based configuration.
You'll just need to create 2 new classes (maybe ServerRegistryProvider and ServerRegistryConfiguration) implementing the correct interfaces.
After that, you'll just need to update the bindings and your whole application stays untouched.

Writing a Test

First, we have the configuration file:

XML
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 <configSections>
   <section name="srvConfig"
     type="Sandrino.MyApplication.ServerConfigurationSection, Sandrino.MyApplication"/>
 </configSections>

 <srvConfig port="1986"/>
</configuration>

As you can see, the server field is missing. But this is a required field! Ooh-ooh!
Let's create a test for this scenario:

C#
[Test]
[ExpectedException(typeof(ConfigurationErrorsException))]
public void ServerMissing()
{
   RegisterConfiguration("Configurations\\ServerMissing.config");
   // Read the file.
   var prov = krn.Get<IServerConfigurationProvider>();
   var section = prov.Read();

   // Assert.
   Assert.IsNull(section.Server);
}

And there you go. We register our provider to use ServerMissing.config. After that, we'll try to read the configuration file and it will raise an exception!

Finally, you can run the test by right clicking on a method:

testrun.png

Take a look in the downloadable solution. It contains a few tests for different scenarios.

I hope you enjoyed the article!

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
Technical Lead RealDolmen
Belgium Belgium
I'm a Technical Consultant at RealDolmen, one of the largest players on the Belgian IT market: http://www.realdolmen.com

All posts also appear on my blogs: http://blog.sandrinodimattia.net and http://blog.fabriccontroller.net

Comments and Discussions

 
QuestionHow would be the best way to Implement a Section Group?? Pin
ReleaseTheHounds22-May-15 6:47
ReleaseTheHounds22-May-15 6:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.