90 lines
2.6 KiB
C#
90 lines
2.6 KiB
C#
|
|
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}.");
|
||
|
|
}
|
||
|
|
}
|