By default, you can't unit test any method that uses ConfigurationManager class like following:
public static string GetFromWebConfig(WebConfigKey key)Note: WebConfigKey is an "enum"
{
var str = ConfigurationManager.AppSettings[key.ToString()];
if (str == null)
{
throw new ApplicationException("Key: " + key.ToString() + " does not exist in the web.config");
}
return str;
}
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;}
}
Create a ConfigurationManagerWrapper class which is to be used for production code:
public class ConfigurationManagerWrapper : IConfigurationManagerNote: the above class uses a thread-safe singleton pattern
{
#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
}
3. Overload your original method
public static string GetFromWebConfig(WebConfigKey key)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.
{
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;
}
4. Write the test
[TestMethod()]Note: NMock has been used.
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();
}
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:
Post a Comment