Unit Test And Time Dependency

Unit Test And Time Dependency

Have a time sensitive application? It can affect unit tests outcomes.

When you start to write unit tests, inevitably you’ll encounter a hard time when a functionality depends on time. Depend on DateTime.Now doesn’t work well for unit testing, your tests will pass on certain time of day and fail in others.

To resolve this problem we need to isolate the time dependency of system, to be able to make our unit test reliable.

Example

So, we have a shop that gives some discount based on the hour of the day, and we have a method that return how much the discount will be:

public class Shop
{
    //Constructor
    //public Shop(.....

    //Other Implementations
    //.....

    public int GetCurrentDiscount()
    {
        switch (DateTime.Now.Hour)
        {
            case 9:
                return 30;
            case 12:
                return 20;
            case 18:
                return 40;
            default:
                return 0;
        }
    }
}

The time dependency is on DateTime.Now.Hour, the discount is given at 9h, 12h, and 18h.

The implementation of a unit test that wants to verify the discount at 9h, looks like this:

[Fact]
public void GetDiscount_9hDiscount_ReturnDiscountForTheHour()
{
    //Arrange
    var sut = new Shop();
    var expectedDiscount = 30;

    //Act
    var result = sut.GetCurrentDiscount();

    //Assert
    Assert.Equal(expectedDiscount, result);
}

Look what happen when I run this test:

Visual Studio Test Runner – Test Red

Unfortunately, the test fails, because it was expecting the 9h discount but it was executed in another time of the day. In this way the test will only pass when the system’s clock hit 9h.

To able to control the result of the test we can create an interface for the DateTime class and inject it in our Shop class via constructor.

The first thing to do is to define the interface, I named the interface ITimeProvider:

public interface ITimeProvider
{
    DateTime Now();
}

So, I simply put in the interface one method that will return DateTime.Now(). You can create an interface with more methods for your needs but for this example I’ll expose just the Now() method.

The concrete implementation of the interface that will be used in our production code will look like this:

public class CurrentTimeProvider : ITimeProvider
{
    public DateTime Now()
    {
        return DateTime.Now;
    }
}

CurrentTimeProvider inherits the interface, and implements the Now() method.

Back to the Shop class, we need to inject the ITimeProvider interface via constructor.

public class Shop
{
    private readonly ITimeProvider _dateTime;

    public Shop(ITimeProvider timeProvider)
    {
        _dateTime = timeProvider;
    }

    //Other Implementations
    //.....

    public int GetCurrentDiscount()
    {
        switch (_dateTime.Now().Hour)
        {
            case 9:
                return 30;
            case 12:
                return 20;
            case 18:
                return 40;
            default:
                return 0;
        }
    }
}

Now in the GetCurrentDiscount method we can use the property just like a instance of the DateTime.

We need to update our unit test now, since we have a new dependency on Shop class. Basically, we are going to use the Moq framework to create a stub for the ITimeProvider, and provide this stub to the Shop class.

[Fact]
public void GetDiscount_9hDiscount_ReturnDiscountForTheHour()
{
    //Arrange
    var timeProviderStub = new Mock<ITimeProvider>();
    timeProviderStub.Setup(t => t.Now()).Returns(new DateTime(2017, 07, 21).AddHours(9));

    var sut = new Shop(timeProviderStub.Object);
    var expectedDiscount = 30;

    //Act
    var result = sut.GetCurrentDiscount();

    //Assert
    Assert.Equal(expectedDiscount, result);
}

Using the Setup method of the mock framework, we can setup the interface’s methods and the methods’s return, as you can see. So as the requirement for our test, Now() will return a date that the hour is 9, when he is called in the test’s execution.

Now when we run the test:

Visual Studio Test Runner - Test Green

The test now passes, because we were capable of isolating the time dependency from our test.

Conclusion

This is a case that you can improve the design of your system to enable unit tests. I think that’s very important because a good code for me, has a lot of different characteristics, and one of then is definitely the ability create unit tests against.

Talking about the time dependency problem in specific, there a couple of different implementations around the internet to resolve this problem, some of them are listed bellow. I prefer the implementation that I showed here, because i think that is a simple implementation and suits well to the problem.

References and Further Reading


© 2021. All rights reserved.

Powered by Hydejack v9.1.6