我在
OfflineHourlyDatabaseBackup
中有一个BackgroundService
.NET 8
,如下所示。
OfflineHourlyDatabaseBackup
:
public class OfflineHourlyDatabaseBackup(
ILogger<OfflineHourlyDatabaseBackup> logger,
TimeProvider timeProvider,
IServiceProvider serviceProvider) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await Task.Yield();
timeProvider.CreateTimer(Backup, null, TimeSpan.Zero, TimeSpan.FromHours(1));
stoppingToken.Register(() => logger.LogWarning($"{nameof(OfflineHourlyDatabaseBackup)} is stopping due to host shut down."));
}
private void Backup(object? state)
{
using var scope = serviceProvider.CreateScope();
var offlineDatabaseBackupService = scope.ServiceProvider.GetRequiredService<IOfflineDatabaseBackupService>();
offlineDatabaseBackupService.Backup();
}
}
我已经编写了统一测试来测试行为。这是我的
xunit
单元测试,
[Fact]
public async Task OfflineHourlyDatabaseBackup_ExecuteAsync()
{
// Arrange
var serviceCollection = new ServiceCollection();
var mockOfflineDatabaseBackupService = new Mock<IOfflineDatabaseBackupService>();
serviceCollection.AddSingleton(_ => mockOfflineDatabaseBackupService.Object);
var now = DateTimeOffset.UtcNow;
var timeProvider = new TestTimeProvider(now);
var serviceProvider = serviceCollection.BuildServiceProvider();
var logger = NullLogger<OfflineHourlyDatabaseBackup>.Instance;
var configuration = new Mock<IConfiguration>();
// Act
using var offlineHourlyDatabaseBackup = new OfflineHourlyDatabaseBackup(logger, timeProvider, serviceProvider);
await offlineHourlyDatabaseBackup.StartAsync(default);
await offlineHourlyDatabaseBackup.ExecuteTask!; // --> Backup() should be called
timeProvider.Advance(TimeSpan.FromHours(1)); // --> Backup() should be called
// Assert
offlineHourlyDatabaseBackup.ExecuteTask.IsCompletedSuccessfully.Should().BeTrue();
mockOfflineDatabaseBackupService.Verify(x => x.Backup(), Times.AtLeast(2));
}
这是我的
TestTimeProvider.cs
,
internal class TestTimeProvider(DateTimeOffset now) : TimeProvider
{
public override DateTimeOffset GetUtcNow() => now;
public override ITimer CreateTimer(TimerCallback callback, object? state, TimeSpan dueTime, TimeSpan period)
{
return base.CreateTimer(callback, state, TimeSpan.Zero, TimeSpan.FromHours(1));
}
public void Advance(TimeSpan timeSpan)
{
now = now.Add(timeSpan);
}
}
当我运行测试时,我希望
Callback
Backup
方法被调用两次。一次是因为 await offlineHourlyDatabaseBackup.ExecuteTask!;
,另一次是因为 timeProvider.Advance(TimeSpan.FromHours(1));
,但收到的异常消息如下
预期对模拟调用至少 2 次,但实际调用次数为 0 次:x => x.Backup()
在调试时,我注意到在第一个
Backup()
调用执行之前,断言行已完成并抛出上述错误。
请您帮助我解决我在这里做错的事情吗?
我终于解决了上述挑战。
我发现
Microsoft
有测试假货,如Nuget
套装。
对于
TimerProvider
- Microsoft.Extensions.TimeProvider.Testing
并简单地更改了测试,如下所示:
FakeTimeProvider
[Fact]
public async Task OfflineHourlyDatabaseBackup_ExecuteAsync()
{
// Arrange
var serviceCollection = new ServiceCollection();
var mockOfflineDatabaseBackupService = new Mock<IOfflineDatabaseBackupService>();
serviceCollection.AddSingleton(_ => mockOfflineDatabaseBackupService.Object);
var now = DateTimeOffset.UtcNow;
var timeProvider = new FakeTimeProvider(); // --> Changed here
var serviceProvider = serviceCollection.BuildServiceProvider();
var logger = new FakeLogger<OfflineHourlyDatabaseBackup>();
var configuration = new Mock<IConfiguration>();
// Act
using var offlineHourlyDatabaseBackup = new OfflineHourlyDatabaseBackup(logger, timeProvider, serviceProvider);
await offlineHourlyDatabaseBackup.StartAsync(default);
await offlineHourlyDatabaseBackup.ExecuteTask!;
timeProvider.Advance(TimeSpan.FromHours(1));
// Assert
offlineHourlyDatabaseBackup.ExecuteTask.IsCompletedSuccessfully.Should().BeTrue();
mockOfflineDatabaseBackupService.Verify(x => x.Backup(), Times.AtLeast(2));
}
来源:https://devblogs.microsoft.com/dotnet/fake-it-til-you-make-it-to-product/