Unit Testing Newbie

When I first started taking unit testing more seriously (it was 13+ years ago, don’t fight me!) I didn’t understand how to test something when I was using DateTime.UtcNow. My tests worked then failed a while later.

I asked, on reflection, an embarrassing question on Stack Overflow. What made it embarrassing was that it showed my lack of understanding of non-deterministic values and testing code that used them. As I mentioned in this case it was usage of DateTime in C#.

The post now has dead links which is unfortunate as this topic came up again in a conversation I had with a fellow developer recently. So here’s a post for reference.

Here’s the approach I learnt and took back then and still do today. Hit me up if there is a more modern approach but this is what I’ve used for years.

The problem

You’re writing a unit test for a method that calculates a person birthday

You come up with something simple like the below:

⚠️ There’s a few things wrong with this method as it’s intended to only highlight the issue! ⚠️ From .NET 6 onwards there is also a new DateOnly type which would be more appropriate for representing birth dates. I might write a post about this in the future.

public class AgeCalculator()
{
    public int GetAge(DateTime birthDate)
    {
        DateTime today = DateTime.UtcNow;
        int age = today.Year - birthDate.Year;

        // If the birthday has not occurred yet this year, subtract one from the age
        if (birthDate.Date > today.AddYears(-age))
        {
            age--;
        }

        return age;
    }
}

Today is the 24th January 2025 and you’re testing that someone born on the 1st February 2000 is 24 years old. This test will pass.

Assert.AreEqual(24, AgeCalculator.GetAge(new DateTime(2000, 2, 1)));

but what happens after the 1st February? I’m going to have to adjust my Assertion to 25 and then increment each year?

The solution

Abstract away the usage of non-deterministic values such as DateTime.UtcNow. This is super simple to do.

public interface IDateTimeProvider
{
    // Interface to abstract the current time, making it testable
    public DateTime UtcNow { get; }
}

with an implementation of the real DateTime.UtcNow:

internal sealed class DateTimeProvider : IDateTimeProvider
{
    // Default implementation that returns the actual current time
    public DateTime UtcNow => DateTime.UtcNow;
}

And then where ever you’re calling DateTime.UtcNow replace with your abstraction.

Like so:

public class AgeCalculator(IDateTimeProvider dateTimeProvider)
{
    public int GetAge(DateTime birthDate)
    {
        DateTime today = dateTimeProvider.UtcNow; // <-- This is now using the provider we created
        int age = today.Year - birthDate.Year;

        // If the birthday has not occurred yet this year, subtract one from the age
        if (birthDate.Date > today.AddYears(-age))
        {
            age--;
        }

        return age;
    }
}

Don’t forget to register in your DI infrastructure, example if you’re using Microsoft.Extensions.DependencyInjection framework.

// Register the implementation with your DI container
// This ensures that your application uses the real DateTimeProvider at runtime / in production
services.AddSingleton<IDateTimeProvider, DateTimeProvider>();

It’s also worth mentioning that this pattern applies to other non-deterministic values too (random numbers, GUIDs, etc.).

That's a wrap. Spotted something incorrect? Let me know! You can comment below, create an issue on the GitHub repository or best of all, make a pull request.