Reputation: 103
Suppose I have the following singleton:
public class ConfigReader
{
static readonly ConfigReader instance = new ConfigReader();
private ConfigReader() { }
public static ConfigReader Instance { get { return instance; } }
public int RecordsPerPage
{
get
{
var configPath = DB.Table<ConfigSource>().GetFieldValue("Path").ToString();
if (configSource == "Database")
return Convert.ToInt32(DB.Table<Configuration>().GetFieldValue("RecordsPerPage"));
else
return Convert.ToInt32(ConfigurationManager.AppSettings["RecordsPerPage"]);
}
}
}
How can I write a unit test to mock the DB.Table & ConfigurationManager.AppSettings calls within this singleton so that I can test it with unit tests?
Actually this leads to another question for TDD: Do we really necessary to consider the testability when we design the system? Seems not every design can align with the TDD principles, like DI, etc. Did I misunderstand something?
Upvotes: 4
Views: 2136
Reputation: 236328
Should we consider the testability when designing system? If you write code which adheres SOLID principles, then your system will be testable. So, simply think about whether you need good object oriented design or not.
Alternative solution - if you are using Dependency Injection framework, then you can focus only on single responsibility for this class - reading configuration values. Second responsibility (keeping single instance of class) will go to DI framework, and you will be able to use simple constructor injection here.
Testing your singleton class - in order to mock class dependencies, you should depend on abstractions instead of implementations. And you should provide implementations via dependency injection. Here you have several options - you can convert RecordsPerPage
property to method and use parameter injection:
public int RecordsPerPage(IConfigSource source)
{
var configPath = source.GetFieldValue("Path").ToString();
if (configPath == "Database")
return Convert.ToInt32(source.GetFieldValue("RecordsPerPage"));
return Convert.ToInt32(ConfigurationManager.AppSettings["RecordsPerPage"]);
}
Now you will be able to pass mock of IConfigSource
to your singleton (Moq sample):
int expected = random.Next();
Mock<IConfigSource> sourceMock = new Mock<IConfigSource>();
sourceMock.Setup(s => s.GetFieldValue("Path")).Returns("Database");
sourceMock.Setup(s => s.GetFieldValue("RecordsPerPage")).Returns(expected);
var reader = ConfigReader.Instance;
var actual = reader.RecordsPerPage(sourceMock.Object);
Assert.That(actual, Is.EqualTo(expected));
sourceMock.VerifyAll();
If you want to reuse IConfigSource
for different calls to singleton, then you can declare property for setting it (property injection):
public IConfigSource Source { get; set; }
And setup source during tests:
var reader = ConfigReader.Instance;
reader.Source = sourceMock.Object;
var actual = reader.RecordsPerPage;
Upvotes: 2