Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using TimeProvider for time mocking #104

Open
wants to merge 3 commits into
base: staging
Choose a base branch
from

Conversation

ivan-srbljinovic
Copy link
Contributor

https://app.productive.io/1-infinum/projects/109718/tasks/task/6554725?filter=NDY0NDcz

Updates unit testing chapter with time mocking using TimeProvider class from .NET 8.

Comment on lines 34 to 45
From .NET 8 Microsoft introduced `TimeProvider` abstract class that should replace our custom layer of abstraction for making our code more testable. Previously, we would have something like `IClockProvider` that we would register in the DI container and inject in our service classes.

``` c#
public interface ITimeProvider
{
public DateTime UtcNow { get; }
}
```

We must also add an implementation of the interface which will be used in the runtime, which is as simple as a service can be:

``` c#
public class TimeProvider : ITimeProvider
{
public DateTime UtcNow => DateTime.UtcNow;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need to keep the old example here. Going forward, our projects will be .NET 8+, which means that we will have the native TimeProvider available in the framework.

Copy link
Contributor

@andrejVuk7 andrejVuk7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job overall, left a couple of suggestions. :)

@@ -29,47 +29,27 @@ public string GetTimeBasedGreeting()

It is easy to think of the test cases for this: we must check for each time of the day that the correct greeting is returned. But as soon as we start writing the tests, we come to an issue: only one greeting can be tested at a certain point in time. This means that we have no way of defining unit tests that will pass every time we run them, even though our code is working perfectly!

The issue here is our static reference to `DateTime.UtcNow`, which returns the current date and time. Unfortunately, despite our incredible technological advancements, we still haven't figured out how to control time. This means that as long as our code is dependent on something as uncontrollable as the unforgiving passage of time, we cannot make it run deterministically.
The issue here is our static reference to `DateTime.UtcNow`, which returns the current date and time.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest merging this sentence with the previous paragraph.


What we _can_ do is add a layer of abstraction between our code and the way we fetch the current time. This can be done by adding an interface that we can easily mock when setting up our tests:
From .NET 8 Microsoft introduced `TimeProvider` abstract class that should replace our custom layer of abstraction for making our code more testable. Previously, we would have something like `IClockProvider` that we would register in the DI container and inject in our service classes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I would also suggest reducing the number of occurrences of the words "our" and 'we". :)

In this example, we're keeping it simple by only adding a single property, but we could expand it if we ever needed some other time-related types, like `DateTimeOffset`, or wanted to get the time in some other timezone.

Don't forget to register the service in the DI configuration! This service can be registered as a singleton since it only passes a static reference, so there is no need to create more than one instance of it:
Since the introduction of `TimeProvider` in .NET 8 we get all that out of the box. All we need to do is register the default implementation of the `TimeProvider` as a singleton and inject it in our service and replace direct use of `DateTime.UtcNow`. Default implementation uses current system clock.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about merging this paragraph with the previous one and reworking it to look somewhat like:

"The old approach included the usage of IClockProvider that we would register in the DI container and inject in our service classes. From .NET 8, Microsoft introduced TimeProvider abstract class that should replace the custom layer of abstraction and make the code more testable. All that needs to be done is:

  • registration of the default implementation of the TimeProvider as a singleton
  • its injection in our service
  • replacing the direct usage of DateTime.UtcNow.

The default implementation uses current system clock."

```

TimerProvider class enables us to do much more than just work with current time, so for more examples check out this [article](https://andrewlock.net/exploring-the-dotnet-8-preview-avoiding-flaky-tests-with-timeprovider-and-itimer/).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I recommend replacing "enables us to do much more" with "makes it possible to do much more". Since this is a technical article, I think it's better to be less personal. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants