Avant .Net 8, tester du code qui utilise DateTime.Now() n’était pas trivial, on devait faire en sorte de mocker la Clock dans nos tests. Depuis .Net 8, c’est beaucoup plus facile grâce à TimeProvider
inclut par défaut !
En deux mots, TimeProvider
est une classe abstraite et on va pouvoir injecter dans nos tests une implémentation “Fake” qui va nous permettre de changer la date actuelle pendant leur exécution.
Exemple concret:
Voici une classe dans laquelle est injecté TimeProvider
(la configuration de l’injection de dépendance via le Program.cs est décrite plus bas)
public ComputeUserResourceCommandHandler(IResourceRepository resourceRepository, TimeProvider timeProvider)
{
_resourceRepository = resourceRepository;
_timeProvider = timeProvider;
}
Pour le contexte, on est dans un jeu ou les ressources sont calculées suivant (entre autre) le temps écoulé depuis la dernière mise à jour.
On n’utilise pas DateTime.Now()
qui est difficile à tester mais _timeProvider.GetUtcNow()
à la place.
public async Task Handle(ComputeUserResourceCommand command, CancellationToken cancellationToken)
{
var userResources = await _resourceRepository.GetResourceForUser(command.UserId);
var elapsedTime = GetElapsedTime(userResources.LastUpdate);
userResources.Food += GetAmountOfResourceToAdd(foodCoefficient, elapsedTime);
userResources.Wood += GetAmountOfResourceToAdd(woodCoefficient, elapsedTime);
userResources.Stone += GetAmountOfResourceToAdd(stoneCoefficient, elapsedTime);
userResources.Gold += GetAmountOfResourceToAdd(goldCoefficient, elapsedTime);
userResources.LastUpdate = _timeProvider.GetUtcNow().DateTime;
}
private long GetElapsedTime(DateTime userResourcesLastUpdate)
{
var lastUpdateTimestamp = new DateTimeOffset(userResourcesLastUpdate).ToUnixTimeSeconds();
var currentTimeStamp = _timeProvider.GetUtcNow().ToUnixTimeSeconds();
return _timeProvider.GetElapsedTime(lastUpdateTimestamp, currentTimeStamp).Ticks;
}
Pour le tester, on n’a qu’à injecter un fake qui étend TimeProvider
et ça tombe bien, Microsoft en propose un déjà tout fait nommé FakeTimeProvider via la lib: Microsoft.Extensions.TimeProvider.Testing.
Il suffit d’installer le Nugget pour pouvoir nous en servir. C’est ce que j’ai fait dans l’exemple.
Voici le test associé:
Scenario: I gain resources over time
Given the current date is 2024-11-18 10:14:00
And I am logged in
And I have the following buildings
| Farm | Sawmill | Gold mine | Stone quarry |
| 1 | 1 | 1 | 1 |
When the current date is 2024-11-18 11:04:00
Then my resources are
| gold | food | wood | stone |
| 52 | 185 | 206 | 204 |
On injecte le FakeTimeProvider
dans son fichier de StepDefinitions
public ResourceStepDefinitions(StrategyGameDbContext strategyGameDbContext, FakeTimeProvider timeProvider)
{
_strategyGameDbContext = strategyGameDbContext;
_timeProvider = timeProvider;
}
et on a une méthode sur-mesure permettant de spécifier la date actuelle sans avoir rien besoin de coder:
[Given(@"the current date is (.*)")]
public void GivenTheCurrentDateIs(DateTime date)
{
_timeProvider.SetUtcNow(date);
}
Il suffit d’’avoir le’utiliser FakeTimeProvider
au lieu de l’implémentation réelle pour ses tests et le tour est joué.
Dans notre Program.cs:
builder.Services.AddSingleton<TimeProvider>(sp =>
builder.Environment.IsEnvironment("Test")
? new FakeTimeProvider()
: TimeProvider.System);
Et c’est tout, à partir de maintenant vous pouvez setter la date dans vos tests grâce à la méthode SetUtcNow
sans aucun code compliqué 🙂