Testing DateTime.Now Revisited: .NET 8.0 TimeProvider

Starting from .NET 8.0, we have new abstractions for time. We don’t need a custom ISystemClock interface. There’s one built-in. Let’s learn how to use the new TimeProvider class to write tests that use DateTime.Now.

.NET 8.0 added the TimeProvider class to abstract date and time. It has a virtual method GetUtcNow() that sets the current time inside tests. It also has a non-testable implementation for production code.

Let’s play with the TimeProvider by revisiting how to write tests that use DateTime.Now.

Back in the day, we wrote two tests to validate expired credit cards. And we wrote an ISystemClock interface to control time inside our tests. These are the tests we wrote:

using FluentValidation;
using FluentValidation.TestHelper;

namespace TimeProviderTests;

[TestClass]
public class CreditCardValidationTests
{
    [TestMethod]
    public void CreditCard_ExpiredYear_ReturnsInvalid()
    {
        var when = new DateTime(2021, 01, 01);
        var clock = new FixedDateClock(when);
        var validator = new CreditCardValidator(clock);
        //                                      ^^^^^
        // Look, ma! I'm going back in time

        var creditCard = new CreditCardBuilder()
                        .WithExpirationYear(DateTime.UtcNow.AddYears(-1).Year)
                        .Build();
        var result = validator.TestValidate(creditCard);

        result.ShouldHaveAnyValidationError();
    }

    [TestMethod]
    public void CreditCard_ExpiredMonth_ReturnsInvalid()
    {
        var when = new DateTime(2021, 01, 01);
        var clock = new FixedDateClock(when);
        var validator = new CreditCardValidator(clock);
        //                                      ^^^^^
        // Look, ma! I'm going back in time again

        var creditCard = new CreditCardBuilder()
                        .WithExpirationMonth(DateTime.UtcNow.AddMonths(-1).Month)
                        .Build();
        var result = validator.TestValidate(creditCard);

        result.ShouldHaveAnyValidationError();
    }
}

public interface ISystemClock
{
    DateTime Now { get; }
}

public class FixedDateClock : ISystemClock
{
    private readonly DateTime _when;

    public FixedDateClock(DateTime when)
    {
        _when = when;
    }

    public DateTime Now
        => _when;
}

public class CreditCardValidator : AbstractValidator<CreditCard>
{
    public CreditCardValidator(ISystemClock systemClock)
    {
        var now = systemClock.Now;
        // Beep, beep, boop
        // Using now to validate credit card expiration year and month...
    }
}

We wrote a FixedDateClock that extended ISystemClock to freeze time inside our tests. The thing is, we don’t need them with .NET 8.0.

1. Use TimeProvider instead of ISystemClock

Let’s get rid of our old ISystemClock by making our CreditCardValidator receive TimeProvider instead, like this:

public class CreditCardValidator : AbstractValidator<CreditCard>
{
    // Before:
    // public CreditCardValidator(ISystemClock systemClock)
    // After:
    public CreditCardValidator(TimeProvider systemClock)
    //                         ^^^^^
    {
        var now = systemClock.GetUtcNow();
        // or
        //var now = systemClock.GetLocalNow();
        
        // Beep, beep, boop
        // Rest of the code here...
    }
}

The TimeProvider abstract class has the GetUtcNow() method to override the current UTC date and time. Also, it has the LocalTimeZone property to override the local timezone. With this timezone, GetLocalNow() returns the “frozen” UTC time as a local time.

If we’re working with Task, we can use the Delay() method to create a task that completes after, well, a delay. Let’s use the short delays in our tests to avoid making our tests slow. Nobody wants a slow test suite.

With the TimeProvider, we can control time inside our tests by injecting a fake. But for production code, let’s use TimeProvider.System. It uses DateTimeOffset.UtcNow under the hood.

person holding glass ball
Time from another perspective. Photo by Jossuha Théophile on Unsplash

2. Use FakeTimeProvider instead of FixedDateClock

We might be tempted to wrie a child class that extends TimeProvider. But, let’s hold our horses. There’s an option for that too.

Let’s rewrite our tests after that change in the signature of the CreditCardValidator.

First, let’s install the Microsoft.Extensions.TimeProvider.Testing NuGet package. It has a fake implementation of the time provider: FakeTimeProvider.

Here are our two tests using the FakeTimeProvider:

using FluentValidation;
using FluentValidation.TestHelper;
using Microsoft.Extensions.Time.Testing;

namespace TestingTimeProvider;

[TestClass]
public class CreditCardValidationTests
{
    [TestMethod]
    public void CreditCard_ExpiredYear_ReturnsInvalid()
    {
        // Before:
        //var when = new DateTime(2021, 01, 01);
        //var clock = new FixedDateClock(when);
        var when = new DateTimeOffset(2021, 01, 01, 0, 0, 0, TimeSpan.Zero);
        var clock = new FakeTimeProvider(when);
        //              ^^^^^
        // Look, ma! No more ISystemClock
        var validator = new CreditCardValidator(clock);
        //                                      ^^^^^

        var creditCard = new CreditCardBuilder()
                        .WithExpirationYear(DateTime.UtcNow.AddYears(-1).Year)
                        .Build();
        var result = validator.TestValidate(creditCard);

        result.ShouldHaveAnyValidationError();
    }

    [TestMethod]
    public void CreditCard_ExpiredMonth_ReturnsInvalid()
    {
        // Before:
        //var when = new DateTime(2021, 01, 01);
        //var clock = new FixedDateClock(when);
        var when = new DateTimeOffset(2021, 01, 01, 0, 0, 0, TimeSpan.Zero);
        var clock = new FakeTimeProvider(when);
        //              ^^^^^
        var validator = new CreditCardValidator(clock);
        //                                      ^^^^^
        // Look, ma! I'm going back in time

        var creditCard = new CreditCardBuilder()
                        .WithExpirationMonth(DateTime.UtcNow.AddMonths(-1).Month)
                        .Build();
        var result = validator.TestValidate(creditCard);

        result.ShouldHaveAnyValidationError();
    }
}

The FakeTimeProvider has two constructors. One without parameters sets the internal date and time to January 1st, 2000, at midnight. And another one that receives a DateTimeOffset. That was the one we used in our two tests.

The FakeTimeProvider has two helpful methods to change the internal date and time: SetUtcNow() and Advance(). SetUtcNow() receives a new DateTimeOffset and Advance(), a TimeSpan to add it to the internal date and time.

If we’re curious, this is the source code of TimeProvider and FakeTimeProvider from the official dotnet repository on GitHub.

If we take a closer look at our tests, we’re “controlling” the time inside the CreditCardValidator. But, we still have DateTime.UtcNow when creating a credit card. For that, we can introduce a class-level constant Now. But that’s an “exercise left to the reader.”

Voilà! That’s how to use the new .NET 8.0 abstraction to test time. We have the new TimeProvider and FakeTimeProvider. We don’t need our ISystemClock and FixedDateClock anymore.

If you want to read more content, check how to Test Logging Messages with FakeLogger and my Unit Testing 101 series where we cover from what a unit test is, to fakes and mocks, to other best practices.

Happy testing!

I applied at a FAANG and failed: Three interviewing lessons

Overconfidence killed all my chances of success.

I applied for a role as a software engineer at a FAANG or MAGMA or insert-newest-acronym here.

And I failed.

I thought: “I have more than 10 years of experience. I’ve seen quite a lot.”

A “short coding assessment” got me off guard. 80 minutes and 3 exercises made me feel like an impostor. An uppercut and 10-second countdown.

I don’t want this post to be another “hiring is broken” and “life is unfair” post. So…

If I could go back in time, this is what I’d tell myself before that coding assesment:

1. Review data structures, especially those you don’t use often.

Take the time to review data structures. Lists, hash maps, queues, trees.

Trees, is this you?

I haven’t used trees since my data structure class back in university. And probably, I wouldn’t use them if I had passed the interview and joined.

But, surprise, surprise. That was one of the questions.

2. Practice using a timer and a coding editor without auto-completion

I know it’s unrealistic. These days, we have IDEs with autocompletion and even AI at our fingertips.

But MAGMAs insist on hiring using coding platforms without autocompletion. The old way.

Since practicing a skill should be as “real” as possible, close your IDE and practice using a bare-bones text editor. And with a timer on.

3. Read all questions first. I know!

Yeah, I wanted to be an A-student playing with the rules. I jumped right to the first question.

50 minutes in and I had barely an answer for the first question. I had to decide between solving only one question or moving on and trying to solve another one. One and a half questions are better than only one, I guess.

I could have nailed the second one first. It was way easier. And definitively, I could have solved the last two questions and skipped the first one. If only I had read all the questions first.

Read all the questions and start with the easy ones. Old advice that I forgot.

Voila! That’s what I’d tell myself before that coding assessment. Yeah, hiring is broken, but we have to go through gatekeepers. Or ditch our CVs and interviewing skills and build a place for ourselves.

For more interview content, read remote interview types and tips and ten tips to solve your next interview coding challenge.

Happy interviewing!

How I read non-fiction books for more retention

These days, I’ve started reading “E-Myth Revisited” by Michael Gerber. I noticed that I developed my own method of reading books. Today, I want to share it: this is how I read non-fiction books for more retention.

To read books, I follow a method based mainly on the Zettelkasten method, described in the book How to Take Smart Notes. With the Zettelkasten method, we keep literature and permanent notes and follow a process to convert literature notes to permanent notes. It boils down to creating connections between notes.

I adapted the Zettelkasten method to use plain text instead of pieces of paper. I’m a big fan of plain text.

The key to retain more from the books we read is to read actively. Read looking for answers and connecting what we read to previous learnings.

This is the six-step process I follow to read non-fiction books.

Step 1: Intention

I switched from reading a book just for the sake of reading to reading a book to answer questions.

I don’t jump into a book with the same attitude if I’m just curious about a subject or want to answer a particular question. I learned I don’t have to read books from cover to cover.

For example, I started reading the “The E-Myth Revisited” to learn how to run a solo consulting practice.

Step 2: Overview

Then, I have a grasp of the book and its content. I use reviews, summaries, podcast interviews, or anything else to understand the overall book content.

Recently, I started to experiment with Copilot for this step. I ask Copilot to generate an executive summary of a book, for example.

Step 3: Note

Then, I create a new Markdown file for the book note. For every note, I use the date and the book title as the title.

I divide each book note into two halves. The first half is for questions and connections, and the second half is for the actual notes.

a rectangle divided into two halves
My note structure and links between notes

Also, I link to the new book note from the book index and the subject index. The book index is a note that links to all books I have read, in alphabetical order. The subject index is a note that references all other notes related to a specific subject. These two types of notes are entry points into my note vault.

For example, I could link to “The E-Myth Revisited” note from a “Consulting” index note.

Step 4: Question

After creating a new note, I read the table of contents, introduction, and conclusion looking for the book’s structure and interesting topics. I skim through the book to find anything that grabs my attention: boxes, graphs, and tables.

In the first half of the book note, I write questions I have about the subject and questions that arose after skimming the book. I got this idea of asking questions about a book before reading from Jim Kwik’s speed reading videos on YouTube.

If I decide not to read the book from cover to cover, I create an index of the chapters and sections I want to read or the ones I don’t want to. I keep this index for future reference. I learned this idea from SuperOrganizers’ Surgical Reading.

Step 5: Read

Then, I read the book while keeping note of interesting parts and quotes in the second half of my book note. I try not to copy and paste passages from the book into my notes but to write things in my own words, except for quotes.

After every chapter, I stop to recall the main ideas from that chapter.

Also, while reading the book, I answer the initial questions in the first half of the note.

Step 6: Connections

While reading or after finishing a chapter, I notice connections with other subjects and my existing knowledge. I use the first half of the note to write these connections and link to other notes.

This is the step where I write my book critique: how this expands or contradicts anything else I’ve learned.

For these connections and critique, the Zettelkasten method recommends a separate set of notes: the permanent notes. The original Zettelkasten proponent used separate handwritten notes and slip boxes for his permanent notes. I keep my permanent notes in the same file but in the first half. This way, the next time I open it, I find my connections and critique first.

Voila! That’s how I read non-fiction books. It’s a combination of the Zettelkasten method with a pre-reading step and my own note structure.

For more content about learning and reading, check my takeaways from Ultralearning, Pragmatic Thinking and Learning, and Show Your Work.

Happy reading!

Software Engineering in 2034: My Predictions

“Prediction is very difficult, especially if it’s about the future.” But here it goes.

On March 12th, 2024, Cognition Labs released Devin, “the first AI software engineer.” This announcement triggered an interesting conversation with a group of colleagues and ex-coworkers. The group was divided into despair and change.

Two opposing views

We all agreed that Devin, and AI in general, won’t take out jobs, at least not in the foreseeable future. But it will change the landscape for sure.

This is where the meme “AI needs well-written and unambiguous requirements, so we’re still safe” holds true.

Before the 2020 pandemic, we were living in a boom. We only needed “software engineer” as the title in our LinkedIn profiles to have dozens of recruiters offering “life-changing opportunities” every week.

That boom is over.

In 2023 and 2024, we experienced massive layoffs. We all knew someone in our inner circle who was laid off. It was a crazy time: one job listing, hundreds of applicants, and radio silence after sending a CV.

One part of the group believed that software engineering, at least the way we know it, would disappear in less than 10 years. They expected to see more layoffs and unemployment. They were planning escape routes away from this industry.

The other part of the group believed the world would still need software engineers, at least, to oversee what AI does. It brought up the subject of working conditions for future software engineers. Maybe they will come from underdeveloped countries with an extremely low wage and poor working conditions to fix the “oops” of AI software engineers.

My own predictions

In 2034, knowing programming and coding by itself won’t be enough. We will need to master a business domain or area of expertise and use programming in that context, mainly with AI.

Rather than being mundane code monkeys, our role will look like Product Managers. AI will automate the coding part of our job. We will work more as Requirement Writers and Prompt Engineers. Essentially, we all will be Engineering Managers overseeing a group of Devin’s.

We will see more Renaissance men and women, well-versed in different areas of knowledge, managing different AIs to achieve the goal of entire teams.

In the meantime, if somebody else writes requirements and we, software engineers, merely translate those requirements into code, we’ll be out of business.

Voilà! That’s how I envision Software Engineering in 2034: more human interaction and business understanding to identify requirements and prompts for AI Software Engineers. No more zero-value tasks like manual testing, code generation, and pointless meetings. AI will handle it all.

This is what Programming was like in 2020 and how I used AI to launch my courses in 2024.

“Open in 2034”

Two new LINQ methods in .NET 9: CountBy and Index

LINQ doesn’t get new features with each release of the .NET framework. It just simply works. This time, .NET 9 introduced two new LINQ methods: CountBy() and Index(). Let’s take a look at them.

1. CountBy

CountBy groups the elements of a collection by a key and counts the occurrences of each key. With CountBy, there’s no need to first group the elements of a collection to count its occurrences.

For example, let’s count all movies in our catalog by release year, of course, using CountBy(),

var movies = new List<Movie>
{
    new Movie("Titanic", 1997, 4.5f),
    new Movie("The Fifth Element", 1997, 4.6f),
    new Movie("Forrest Gump", 1994, 4.3f),
    new Movie("Terminator 2", 1991, 4.7f),
    new Movie("Armageddon", 1998, 3.35f),
    new Movie("Platoon", 1986, 4),
    new Movie("My Neighbor Totoro", 1988, 5),
    new Movie("Pulp Fiction", 1994, 4.3f),
};

var countByReleaseYear = movies.CountBy(m => m.ReleaseYear);
//                              ^^^^^^^
foreach (var (year, count) in countByReleaseYear)
{
    Console.WriteLine($"{year}: [{count}]");
}

// Output
// 1997: [2]
// 1994: [2]
// 1991: [1]
// 1998: [1]
// 1986: [1]
// 1988: [1]

Console.ReadKey();

record Movie(string Name, int ReleaseYear, float Rating);

CountBy() returns a collection of KeyValuePair with the key in the first position and the count in the second one.

By the way, if that Console application doesn’t look like one, it’s because we’re using three recent C# features: the Top-level statements, records, and global using statements.

Before .NET 9.0, we needed to use GroupBy() with a second parameter to transform each group, like this,

var countByReleaseYear = movies.GroupBy(
  x => x.ReleaseYear,
  (releaseYear, movies) => new
  // ^^^^^
  {
      Year = releaseYear,
      Count = movies.Count()
      //      ^^^^^
  });

CountBy() has the same spirit of DistinctBy, MinBy, MaxBy, and other LINQ methods from .NET 6.0. With these methods, we apply an action direcly on a collection using a key selector. We don’t need to filter or group a collection first to apply that action.

Cinematographer's room
Photo by Noom Peerapong on Unsplash

2. Index

Index projects every element of a collection alongside its position in the collection.

Let’s “index” our catalog of movies,

var movies = new List<Movie>
{
    new Movie("Titanic", 1998, 4.5f),
    new Movie("The Fifth Element", 1997, 4.6f),
    new Movie("Terminator 2", 1991, 4.7f),
    new Movie("Avatar", 2009, 5),
    new Movie("Platoon", 1986, 4),
    new Movie("My Neighbor Totoro", 1988, 5)
};

foreach (var (index, movie) in movies.Index())
//                                    ^^^^^
{
    Console.WriteLine($"{index}: [{movie.Name}]");
}

// Output
// 0: [Titanic]
// 1: [The Fifth Element]
// 2: [Terminator 2]
// 3: [Avatar]
// 4: [Platoon]
// 5: [My Neighbor Totoro]

Console.ReadKey();

record Movie(string Name, int ReleaseYear, float Rating);

Unlike CountBy(), Index() returns named tuples. It returns IEnumerable<(int Index, TSource Item)>.

Before, we had to use the Select() overload or roll our own extension method. In fact, this is one of the helpful extension methods I use to work with collections. But I call it Enumerated().

If we take a look at the Index source code on GitHub, it’s a foreach loop with a counter in its body. Nothing fancy!

Voilà! Those are two new LINQ methods in .NET 9.0: CountBy() and Index(). It seems the .NET team is bringing to the standard library the methods we needed to roll ourselves before.

To learn about LINQ and other methods, check my quick guide to LINQ, five common LINQ mistakes and how to fix them, and peeking into LINQ DistinctBy source code.

If you want to write more expressive code to work with collections, check my course Getting Started with LINQ on Educative, where I cover from what LINQ is, to refactoring conditionals with LINQ and to the its new methods and overloads in .NET6. All you need to know to start using LINQ in your everyday coding.

Happy coding!