It is easy enough to create unit tests for conditional blocks which follow the expected path, but it can sometimes be difficult to contrive data for sources/objects I do not directly control, (databases I do not want to modify or access, environmental variables, etc.) without modifying the source code to add debugging logic (using only unit tests as functions. How would one construct unit tests to test the following specified control blocks?
function(int x)
{
if(x > 10)
{
if(system.day() == "Monday")
print "Monday"
else
// TEST THIS SPACE (BUT ON A MONDAY :)
print "Not Monday"
}
else
{
....
}
}
In the previous function, I can write a function and pass whatever value I like in for x, but how can I test the nested conditional which relies on a a System.date() call that I cannot (easily) modify?
Another example using databases which I do not control and cannot access:
function(int x)
{
try
{
if(x > 10)
{
query_result = database.query()
if(query_results != NULL)
{
print "QUERY NOT NULL"
}
else
{
// TEST THIS SPACE
print "QUERY IS NULL"
}
}
}
}
Obviously, if I was passing in the query, I could control it, but in this case I cannot. These are simple and contrived examples, please expand on these particular cases and any related scenarios which I may not have considered.
Best Answer
You want to use dependency injection. I'll give you an example in C#, since I'm most familiar with it. Your first example:
where the two services are defined as following
and implemented like this:
Now in your program, in your main function (also known as 'composition root' using dependency injection terminology), you would instantiate your class as following:
However, if you wanted to test your code, instead of injecting RealTimeService and RealOutputService, you would inject FakeTimeService, and FakeOutputService. For example, this FakeTimeService will let you return any day of the week you want:
and this FakeOutputService will store whatever was output to it, so your test can verify it was correct:
Finally, inside your test code you'd instantiate and test the class as follows:
Now granted, this seems like a lot of typing, which is why most of the time you'd use a mocking framework to do it for you. For example in C#, I would use Moq framework, which takes care of creating fake objects for you.
Also, if you want another DI in Unit Testing example, check out my answer from a few months ago here.