books/backend/Books.Api.Tests/Helpers/Eventually.cs

90 lines
2.6 KiB
C#
Raw Normal View History

namespace Books.Api.Tests.Helpers;
/// <summary>
/// Helper for testing eventually consistent systems.
/// Polls a condition until it's met or times out.
/// </summary>
public static class Eventually
{
/// <summary>
/// Polls a condition until it returns true or times out.
/// </summary>
public static async Task AssertAsync(
Func<Task<bool>> condition,
TimeSpan? timeout = null,
TimeSpan? pollInterval = null,
string? failMessage = null)
{
timeout ??= TimeSpan.FromSeconds(5);
pollInterval ??= TimeSpan.FromMilliseconds(50);
var start = DateTime.UtcNow;
while (DateTime.UtcNow - start < timeout)
{
if (await condition())
return;
await Task.Delay(pollInterval.Value);
}
throw new TimeoutException(
failMessage ?? $"Condition was not met within {timeout}.");
}
/// <summary>
/// Polls until a non-null result is returned or times out.
/// </summary>
public static async Task<T> GetAsync<T>(
Func<Task<T?>> getter,
TimeSpan? timeout = null,
TimeSpan? pollInterval = null,
string? failMessage = null) where T : class
{
timeout ??= TimeSpan.FromSeconds(10);
pollInterval ??= TimeSpan.FromMilliseconds(100);
var start = DateTime.UtcNow;
while (DateTime.UtcNow - start < timeout)
{
var result = await getter();
if (result != null)
return result;
await Task.Delay(pollInterval.Value);
}
throw new TimeoutException(
failMessage ?? $"Expected non-null result was not returned within {timeout}.");
}
/// <summary>
/// Polls until a collection has the expected count or times out.
/// </summary>
public static async Task<IReadOnlyList<T>> GetListAsync<T>(
Func<Task<IReadOnlyList<T>>> getter,
int expectedCount,
TimeSpan? timeout = null,
TimeSpan? pollInterval = null,
string? failMessage = null)
{
timeout ??= TimeSpan.FromSeconds(10);
pollInterval ??= TimeSpan.FromMilliseconds(100);
var start = DateTime.UtcNow;
while (DateTime.UtcNow - start < timeout)
{
var result = await getter();
if (result.Count >= expectedCount)
return result;
await Task.Delay(pollInterval.Value);
}
throw new TimeoutException(
failMessage ?? $"Expected {expectedCount} items but condition was not met within {timeout}.");
}
}