Introduction
One thing that we should all do more is write unit tests. But sometimes, it is hard to write tests for your code. Recently, I was testing one method which used static class from .NET Framework and I didn’t know how to test it.
It was ConfigurationManager class.
In my earlier post, I talked about reading Web.config file using ConfigurationManager
class. And today, I would like to show you how to improve this class even more, by using interface instead of static
class.
And as a next step, how easy it is to test this interface.
Static Class
Imagine you are working on a project and you are asked to create class UrlCreator
with one method.
public string CreateUrlWithoutInterface(string[] parameters)
{
StringBuilder sb = new StringBuilder();
sb.Append(ConfigurationManager.AppSettings["baseurl"]);
sb.Append("?");
for (int i = 0; i < parameters.Length; i++)
{
sb.Append(string.Format("p{0}={1}", i+1, parameters[i]));
if(i != parameters.Length - 1)
{
sb.Append("&");
}
}
return sb.ToString();
}
This method uses ConfigurationManager
on line 4. This method could be written better, but for the sake of this example, let's pretend it is a complicated method and you need to write the tests to verify the functionality.
However, when we want to test this method, we run into trouble.
[TestMethod]
public void CreateUrl_noInterface()
{
var creator = new UrlCreator(null);
var parameters = new string[] { "a", "b", "c" };
var result = creator.CreateUrlWithoutInterface(parameters);
Assert.AreEqual("www.test.com?p1=a&p2=b&p3=c", result);
}
We are missing the baseurl
path that is saved in a config file because there is no config file in Test
project.
So how do we fix this? Well, we can use the interface and mock the method.
Interface
Same method but with using interface:
public class UrlCreator
{
IConfigurationManager _config;
public UrlCreator(IConfigurationManager config)
{
_config = config;
}
public string CreateUrlWithInterface(string[] parameters)
{
StringBuilder sb = new StringBuilder();
sb.Append(_config.GetAppSetting("baseurl"));
sb.Append("?");
for (int i = 0; i < parameters.Length; i++)
{
sb.Append(string.Format("p{0}={1}", i + 1, parameters[i]));
if (i != parameters.Length - 1)
{
sb.Append("&");
}
}
return sb.ToString();
}
}
Here, we need to inject our IConfigurationManager
into UrlCreator
class. We can do this by simply using Dependency Injection Framework, or we can do it manually as I do it in the demo project.
In the method with the interface on line 4, we can see the change from static class ConfigurationManager
to interface IConfigurationManager
.
[TestMethod]
public void CreateUrl_interface()
{
var config = new FakeReader();
var creator = new UrlCreator(config);
var parameters = new string[] { "a", "b", "c" };
var result = creator.CreateUrlWithInterface(parameters);
Assert.AreEqual("www.test.com?p1=a&p2=b&p3=c", result);
}
The magic here is in the interface. You can create a custom implementation of the interface and therefore easily mock the interface. In the demo project, I am using the FakeReader
class that returns predictable data for the test.
FakeReader
public class FakeReader : IConfigurationManager
{
public string GetAppSetting(string name)
{
return "www.test.com";
}
public string GetConnectionString(string name)
{
throw new NotImplementedException();
}
}
Little side note here. You don’t have to create a separate class here in order to mock the method. You could easily use some mocking framework like Moq. But that is out of the scope of this article.
Summary
If you need to test a method that uses static
class or method, try to decouple that static
element into interface and than mock the interface instead. The functionality of the static
method should be short and predictable, because you are also taking this piece of code out of unit testing. And in order to test this static
method, you should use some kind of acceptance test.
You can download the whole project here.