Friday, 7 August 2009

Unit Testing - How to unit test a method that uses ConfigurationManager?

Problem:

By default, you can't unit test any method that uses ConfigurationManager class like following:

public static string GetFromWebConfig(WebConfigKey key)
{
var str = ConfigurationManager.AppSettings[key.ToString()];
if (str == null)
{
throw new ApplicationException("Key: " + key.ToString() + " does not exist in the web.config");
}
return str;
}
Note: WebConfigKey is an "enum"

My Solution: Enable Dependency Injection

1. Create an Interface

Create an interface that provides you the properties which are needed by ConfigurationManager

public interface IConfigurationManager
{
NameValueCollection AppSettings {get;}
}
2. Create a Wrapper

Create a ConfigurationManagerWrapper class which is to be used for production code:

public class ConfigurationManagerWrapper : IConfigurationManager
{
#region Fields and Properties

static ConfigurationManagerWrapper instanceHolder = new ConfigurationManagerWrapper();

public static ConfigurationManagerWrapper Instance
{
get { return instanceHolder; }
}

public NameValueCollection AppSettings
{
get
{
return ConfigurationManager.AppSettings;
}
}

#endregion

#region Constructors

private ConfigurationManagerWrapper()
{
}
#endregion
}
Note: the above class uses a thread-safe singleton pattern

3. Overload your original method
public static string GetFromWebConfig(WebConfigKey key)
{
return GetFromWebConfig(key, ConfigurationManagerWrapper.Instance);
}

public static string GetFromWebConfig(WebConfigKey key, IConfigurationManager configurationManager)
{
var str = configurationManager.AppSettings[key.ToString()];
if (str == null)
{
throw new ApplicationException("Key: " + key.ToString() + " does not exist in the web.config");
}
return str;
}
Note: the first method is to be used directly by the production code and passes a singleton instance of ConfigurationManagerWrapper. The second code will be used directly by the testing framework to benefit from dependency injection.

4. Write the test

[TestMethod()]
public void GetFromWebConfigTest()
{
// setup
var key = WebConfigKey.SecondsToCache;
var expected = "60";
mocks = new Mockery();
var configurationManager = mocks.NewMock();
Expect.Once.On(configurationManager).GetProperty("AppSettings")
.Will(Return.Value(new NameValueCollection {{key.ToString(), expected}}));

// exercise
var actual = Utils.GetFromWebConfig(key, configurationManager);

// verify
Assert.AreEqual(expected, actual);
mocks.VerifyAllExpectationsHaveBeenMet();
}
Note: NMock has been used.

Challenging Myself:
How is it possible to keep the original method and just create the wrapper and the interface at the time the test runs only so that we don't need to make any changes to the production code? I guess using Reflection, etc.

No comments: