Skip to content

Parallel inspects with ThenAll

After an act, you often need to check several independent effects: the API read, the database row, the published event. Checked one after another they serialise — and each may involve a poll with a timeout, so the waits add up. ThenAll runs a group of inspects concurrently, then the chain continues in order.

The most readable form takes branch builders — each branch is its own little inspect sub-chain:

await Inspect
.WriteResult(result).Created()
.Ensure(result, r => r.ClientId, out var clientId)
.ThenAll(
b => b.ApiClient(clientId, c => c.Name.ShouldBe("Acme Corporation")),
b => b.DbClient(clientId, c => c.ShouldNotBeNull()),
b => b.EventPublished("clients.created", clientId));

The three observations run at once; the slowest one determines how long the group takes, not the sum. A branch can chain multiple steps — b => b.Then(...).Then(...) — and they run within that branch in order.

ThenAll is a concurrent group inside an otherwise ordered chain. Steps before it complete first; steps after it run only once every branch has finished:

await Inspect
.Then(_ => Add(1))
.Then(_ => Add(2))
.ThenAll(b => b.Then(_ => Add(3)), b => b.Then(_ => Add(4))) // 3 and 4 in either order
.Then(_ => Add(5)); // strictly after both
// order: 1, 2, {3,4 in some order}, 5

Concurrent resolves from the stage are safe — the container hands every branch the same cached instance — so even a high fan-out (dozens of branches all resolving the same service) is race-free.

ThenAll also accepts plain inspect functions when you don’t need sub-chains:

ITestInspect ThenAll(params Func<ITestInspect, ITestInspect>[] branches); // branch builders (shown above)
ITestInspect ThenAll(params InspectAsyncFn[] inspectFns); // async funcs
ITestInspect ThenAll(params InspectFn[] inspectFns); // sync funcs