DEV Community

Jan Van Ryswyck
Jan Van Ryswyck

Posted on • Originally published at principal-it.eu on

Dealing With Date/Time In Solitary Tests

Solitary tests never cross the process boundary in which they are executed. This means that a solitary test never executes code that talks to a database, communicates across the network, touches the file system, etc. This also implies that a solitary test never deals with the system clock either, whether it’s directly or indirectly.

Let’s have a look at some example code in order to grasp the implications of this.

public class CreateCouponHandler
{
    private readonly ICouponRepository _couponRepository;

    public CreateCouponHandler(ICouponRepository couponRepository)
    {
        _couponRepository = couponRepository;
    }

    public void Handle_Coupled(CreateCoupon command)
    {
        var coupon = new Coupon(command.CouponCode, DateTime.Today);
        _couponRepository.Save(coupon);
    }
}

public class CreateCoupon
{
    public string CouponCode { get; }

    public CreateCoupon(string couponCode)
    {
        CouponCode = couponCode;
    }
}

Here we have a handler that creates a new discount coupon and saves it in the database. A coupon requires a code, and a creation date. The coupon code is specified through an incoming CreateCoupon command object. The creation date is set to the current date by calling the DateTime.Today static property.

Let’s have a look at the test code.

[Specification]
public class When_creating_a_new_coupon
{
    [Establish]
    public void Context()
    {
        _couponRepository = Substitute.For<ICouponRepository>();
        _sut = new CreateCouponHandler(_couponRepository);
    }

    [Because]
    public void Of()
    {
        var command = new CreateCoupon("COUPON_CODE");
        _sut.Handle(command);
    }

    [Observation]
    public void Then_a_newly_created_coupon_should_be_saved()
    {
        var expectedCouponToBeSaved = new Coupon("COUPON_CODE", DateTime.Today);
        _couponRepository.Should_have_received(expectedCouponToBeSaved);        
    }

    private ICouponRepository _couponRepository;
    private CreateCouponHandler _sut;
}

According to its definition, this test doesn’t entirely qualify as a solitary test. Sure, it doesn’t require any external configuration file in order for it to execute properly. However, both the implementation and the test code have a direct dependency on the system clock.

This test will pass most of the time. However, on rare occasions this test might also fail. Why? Suppose that this testis being executed on a build server a couple of milliseconds before midnight. The call to DateTime.Today in the Handle_method of the _CreateCouponHandler class returns the current date. When the test method itself is executed, midnight has already passed. So the call to DateTime.Today returns the date of the next day which causes the test to fail.

I do recognise that the chances are pretty slim for this to actually happen. Nonetheless, this definitely affects the reliability of the test. Suppose that besides the date, we also need to store the time when a coupon gets created. In that case, we would replace the calls to DateTime.Today with DateTime.UtcNow. This would also make the test even less deterministic as it would sometimes fail due to a potential difference of just a couple of milliseconds.

In order to make both the implementation and the test code more loosely coupled, we could remove the direct dependency on the system clock by encapsulating the calls to DateTime.Today and DateTime.UtcNow.

public interface IClock
{
    DateTime GetCurrentDate();
    DateTime GetCurrentDateTime();
}

Here we’ve defined an interface named IClock. This defines a contract for retrieving the current date, or the current date/time.

public class SystemClock : IClock
{
    public DateTime GetCurrentDate()
    {
        return DateTime.Today;
    }

    public DateTime GetCurrentDateTime()
    {
        return DateTime.UtcNow;
    }
}

The SystemClock class provides an implementation of the IClock interface by encapsulating the calls to DateTime.Today and DateTime.UtcNow.

public class CreateCouponHandler
{
    private readonly ICouponRepository _couponRepository;
    private readonly IClock _clock;

    public CreateCouponHandler(ICouponRepository couponRepository, IClock clock)
    {
        _couponRepository = couponRepository;
        _clock = clock;
    }

    public void Handle(CreateCoupon command)
    {
        var creationDate = _clock.GetCurrentDate();

        var coupon = new Coupon(command.CouponCode, creationDate);
        _couponRepository.Save(coupon); 
    }
}

An instance of the IClock interface is injected into the constructor of the CreateCouponHandler class. The IoC container of the application will provide the necessary instance of the SystemClock class. In order to get the current date, we just call the GetCurrentDate method.

Let’s see what this means for our test code.

[Specification]
public class When_creating_a_new_coupon
{
    [Establish]
    public void Context()
    {
        _couponRepository = Substitute.For<ICouponRepository>();

        // The clock stub returns a concrete date
        var clock = Substitute.For<IClock>();
        clock.GetCurrentDate().Returns(new DateTime(2020, 08, 01));

        _sut = new CreateCouponHandler(_couponRepository, clock);
    }

    [Because]
    public void Of()
    {
        var command = new CreateCoupon("COUPON_CODE");
        _sut.Handle(command);
    }

    [Observation]
    public void Then_a_newly_created_coupon_should_be_saved()
    {
        // Here we specify a concrete date for the creation date
        var expectedCouponToBeSaved = new Coupon("COUPON_CODE", new DateTime(2020, 08, 01));
        _couponRepository.Should_have_received(expectedCouponToBeSaved);        
    }

    private ICouponRepository _couponRepository;
    private CreateCouponHandler _sut;
}

Notice that we no longer have a dependency on DateTime.Today inside the test method. Instead, we just specify a concrete date.

var expectedCouponToBeSaved = new Coupon("COUPON_CODE", new DateTime(2020, 08, 01));
_couponRepository.Should_have_received(expectedCouponToBeSaved);

In the setup of the test context, we also specify that a call to the GetCurrentDate method would returnthe same concrete date.

var clock = Substitute.For<IClock>();
clock.GetCurrentDate().Returns(new DateTime(2020, 08, 01));

By removing the direct dependency on the system clock, we turned our test into a full-fledged solitary test. Not only that. We’ve also made the test itself more deterministic as well. The test no longer suffers from potential failures depending on the time of day that it’s being executed. And then there’s also the aspect of readability.

Making use of DateTime.Today or DateTime.UtcNow lacks readability as a developer has to mentally translate this to a concrete date. Removing this mental translation step just makes things more convenient for other developers on the team.Especially when there’s some kind of date arithmetic involved, making use of a concrete date and/or time communicates more clearly what the inputs are and what the expected outputs should be.

Top comments (0)