如何在c# xunit单元测试中断言定时器回调

问题描述 投票:0回答:1

我在

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()
调用执行之前,断言行已完成并抛出上述错误。

请您帮助我解决我在这里做错的事情吗?

c# .net unit-testing timer xunit
1个回答
0
投票

我终于解决了上述挑战。

我发现

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/

© www.soinside.com 2019 - 2024. All rights reserved.