namespace Books.Api.Tests.Helpers;
///
/// Helper for testing eventually consistent systems.
/// Polls a condition until it's met or times out.
///
public static class Eventually
{
///
/// Polls a condition until it returns true or times out.
///
public static async Task AssertAsync(
Func> 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}.");
}
///
/// Polls until a non-null result is returned or times out.
///
public static async Task GetAsync(
Func> 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}.");
}
///
/// Polls until a collection has the expected count or times out.
///
public static async Task> GetListAsync(
Func>> 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}.");
}
}