To start with, let us solve the dilemma set out at the
beginning - testing against the system time. A method,
GetImageForTimeOfDay(), should return a filename to a image based on the
time of day it currently is; for example, sun.jpg for daytime and a moon.jpg
for night time.
This is how the tests might look if we were not using
mock objects. I have cut down a lot of the code which would actually be
required, for example taking into account timezones and time of year.
Listing 1
[Test]
public void DaytimeTest()
{
int currentHour = DateTime.Now.Hour;
if (currentHour > 6 && currentHour < 21)
{
string expectedImagePath = "sun.jpg";
ImageManagement image = new ImageManagement();
string path = image.GetImageForTimeOfDay();
Assert.AreEqual(expectedImagePath, path);
}
else
{
Assert.Ignore("Can only be tested during the day");
}
}
[Test]
public void NighttimeTest()
{
int currentHour = DateTime.Now.Hour;
if (currentHour < 6 || currentHour > 21)
{
string expectedImagePath = "moon.jpg";
ImageManagement image = new ImageManagement();
string path = image.GetImageForTimeOfDay();
Assert.AreEqual(expectedImagePath, path);
}
else
{
Assert.Ignore("Can only be tested at night");
}
}
The method could be implemented like this:
Listing 2
public string GetImageForTimeOfDay()
{
int currentHour = DateTime.Now.Hour;
if (currentHour > 6 && currentHour < 21)
return "sun.jpg";
else
return "moon.jpg";
}
This is far from ideal as we can only ever run one test
at a time as they can only be run at certain points in the day. If we
change the code, we can only test one part of the actual requirement, unless we
modify the system clock. Having difficult to execute tests is just one reason
why we might stop running the tests altogether. How can we trust the
application even works?
By using a mock object and abstracting away from
DateTime.Now.Hour we will be able to have much more reliable tests. In
order to be able to use mock objects, we need to a use an interface design
for obtaining the time. Rhino Mocks then uses this interface to create the mock
implementation of the object which allows us to call the methods on the
interface just like we would with a real implementation.
We then use parameter injection to define which
implementation should be used within the method.
When using mock objects, the tests would look like this.
Listing 3
[Test]
public void DaytimeTest()
{
MockRepository mocks = new MockRepository();
IDateTime timeController = mocks.CreateMock<IDateTime>();
using (mocks.Record())
{
Expect.Call(timeController.GetHour()).Return(15);
}
using (mocks.Playback())
{
string expectedImagePath = "sun.jpg";
ImageManagement image = new ImageManagement();
string path = image.GetImageForTimeOfDay(timeController);
Assert.AreEqual(expectedImagePath, path);
}
}
[Test]
public void NighttimeTest()
{
MockRepository mocks = new MockRepository();
IDateTime timeController = mocks.CreateMock<IDateTime>();
using (mocks.Record())
{
Expect.Call(timeController.GetHour()).Return(1);
}
using (mocks.Playback())
{
string expectedImagePath = "moon.jpg";
ImageManagement image = new ImageManagement();
string path = image.GetImageForTimeOfDay(timeController);
Assert.AreEqual(expectedImagePath, path);
}
}
With the implementation for the system now being:
Listing 4
public interface IDateTime
{
int GetHour();
}
public class ImageManagement
{
public string GetImageForTimeOfDay(IDateTime time)
{
int currentHour = time.GetHour();
if (currentHour > 6 && currentHour < 21)
{
return "sun.jpg";
}
else
{
return "moon.jpg";
}
}
}
Now we have a set of useful tests which can be executed
no matter what time it is, however, the mock syntax may look very
different to you.
In the first line we are defining a MockRepository
which is the main interaction with the Rhino Mocks framework.
Listing 5
MockRepository mocks = new MockRepository();
We then define our mock object. There are two methods
for creating the mock, one is CreateMock<> while the other is
DynamicMock<>. According to the documentation,
CreateMock has strict semantics, meaning if a call is made on the mock object
which it was not expecting then it will throw an exception. DynamicMock
has dynamic semantics where unexpected calls are accepted with a null/zero
value being returned and no exception is thrown.
Listing 6
IDateTime timeController = mocks.CreateMock<IDateTime>();
Next, we enter recording mode for the mock object. This
is an important stage where we define what we want the mock object to do. We
define the calls we expect to be made on the object and the values which
should be returned. This is how we can create a known state for our system
as everything is defined in recording mode. In this example we are saying that
we expect that the GetHour() method will be called on our mock implementation
(timeController) and that this should always return the value 15 to the calling
code.
Listing 7
using (mocks.Record())
{
Expect.Call(timeController.GetHour()).Return(15);
}
From a Test Driven Development (TDD) approach, this
encourages us to focus on the design a lot more upfront as we define all of the
method calls and the return values.
After we have defined all of our expectations we enter
Playback mode. In this mode we write the unit test as normal.
Listing 8
using (mocks.Playback())
{
string expectedImagePath = "sun.jpg";
ImageManagement image = new ImageManagement();
string path = image.GetImageForTimeOfDay(timeController);
Assert.AreEqual(expectedImagePath, path);
}
Notice how the GetImageForTimeOfDay() method now takes
in the IDateTime object as a parameter. This approach allows us a lot
more flexibility and allows us to define which implementation, either the mock
object or real, at runtime it should use instead of the method deciding
for us. If we wanted to clean up the code for production, we could
overload the method with no parameters, which simply initializes the default
implementation and passes it in as an argument.
Finally, now we have written the test, we can write the code
to make them pass.
Listing 9
public string GetImageForTimeOfDay(IDateTime time)
{
int currentHour = time.GetHour();
if (currentHour > 6 && currentHour < 21)
{
return "sun.jpg";
}
else
{
return "moon.jpg";
}
}
public class DateTimeController : IDateTime
{
public int GetHour()
{
return DateTime.Now.Hour;
}
We are still calling DateTime.Now.Hour, however our
GetImageForTimeOfDay() is no longer dependant on this.
An application could then look like this:
Listing 10
static void Main(string[] args)
{
ImageManagement image = new ImageManagement();
string path = image.GetImageForTimeOfDay(new DateTimeController());
Console.WriteLine(path);
Console.ReadLine();
}
Hopefully from this, you can see that using Rhino Mocks
greatly increases the capabilities of the tests which we are writing, allowing
us to replace the hard to test components. Also, mock objects have
improved the design of the system by encouraging us to program against
interfaces, one of the core OO design rules, and abstracting away from the core
framework. If we have to change the way we handle obtaining the time,
maybe from a external time server, we only have to change the internal method
in our DateTimeController.