-
Notifications
You must be signed in to change notification settings - Fork 1
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
base: staging
Are you sure you want to change the base?
Conversation
Testing/Unit Tests.md
Outdated
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; | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this 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. |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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/). |
There was a problem hiding this comment.
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. :)
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.