Understanding Mock Objects: an alternate solution
In AzamSharp’s recent post Understanding Mock Objects, he poses a problem of testing with volatile data. His example extends on an article on AspAlliance, which exhibits the same problems with its solution. Suppose I have an image service that returns images based on the time of day:
public static class ImageOfTheDayService { public static string GetImage(DateTime dt) { int hour = dt.Hour; if (hour > 6 && hour < 21) return "sun.jpg"; return "moon.jpg"; } }
The initial test uses the current date and time to perform the test:
[Test] public void should_return_sun_image_when_it_is_day_time() { string imageName = ImageOfTheDayService.GetImage(DateTime.Now); Assert.AreEqual(imageName, "sun.jpg"); }
Since DateTime.Now is non-deterministic, this test will pass only some of the time, and will at other times. The problem is that this test has a dependency on the system clock. AzamSharp’s solution to this problem was to create an interface to wrap DateTime:
public interface IDateTime { int GetHour(); }
Now the ImageOfTheDayService uses IDateTime to determine the hour:
[Test] public void MOCKING_should_return_night_image_when_it_is_night_time() { var mock = new Mock<IDateTime>(); mock.Expect(e => e.GetHour()).Returns(21); // 9:00 PM Assert.AreEqual("moon.jpg", ImageOfTheDayService.GetImage(mock.Object)); }
I really don’t like this solution, as the test had the external non-deterministic dependency, not the image service.
Alternative solution
Here’s another solution that doesn’t use mocks, and keeps the original DateTime parameter:
[Test] public void should_return_night_image_when_it_is_night_time() { DateTime nightTime = new DateTime(2000, 1, 1, 0, 0, 0); string imageName = ImageOfTheDayService.GetImage(nightTime); Assert.AreEqual(imageName, "moon.jpg"); }
Note that I eliminated the dependency on the system clock by simply creating a DateTime that represents “night time”. Another test creates a “day time” DateTime, and perhaps more to fill in edge cases. Instead of creating an interface to wrap something that didn’t need fixing, we used DateTime exactly how they were designed. DateTime.Now is not the only way to create a DateTime object.
Solving a non-deterministic test with mocks only works when it’s the component under test that has the non-deterministic dependencies. In the example AzamSharp’s example, it was the test, and not the component that had the non-deterministic dependency. Creating the DateTime using its constructor led to both a more readable test and a more cohesive interface for the image service.
It’s easy to believe everything is a nail if all you want to use is that shiny hammer. Keep in mind the purpose of mocks: to verify the indirect inputs and outputs of a component, not to fix every erratic test under the sun.