Generating unique values in FsCheck

Nick Lydon
2 min readSep 15, 2022
Snowflake by Alexey Kljatov

FsCheck is handy for generating pseudo-random values whilst testing, but one thing that’s not particularly intuitive is how to generate random, unique values. A potential use case is generating a collection of domain objects, such as suppliers, which each have a unique identifier. There are several trivial cases, such as generating a GUID for an id, or using Arb.Default.Set<T> to produce a set of a particular type. If you want more control over how they’re generated then it’s a bit more involved.

Let’s take an example of producing a collection of suppliers, each with a unique id and a boolean value indicating whether they have products in stock. The supplier identifiers can simply be a list of sequential integers. Here’s how you could define a recursive method to generate them:

Gen<IEnumerable<(int SupplierId, bool OutOfStock)>> SuppliersGenerator(int max, int current = 0)
{
if (current >= max) return Gen.Constant(Enumerable.Empty<(int SupplierId, bool OutOfStock)>());

return
from next in SuppliersGenerator(max, current + 1)
from outOfStock in Arb.Default.Bool().Generator
select next.Prepend((current, outOfStock));
}

As with all recursive methods we have two cases:

  1. The base case, when the counter has exceeded max, returns a generator that produces an empty collection
  2. The other case calls the method recursively, incrementing the counter to reach the base case, and prepends the counter value with a random boolean to the rest of the collection that’s being generated

The example of a sequential integer list is quite simple. It could be adapted to any use case by passing a hash set, generating values and only accepting the ones that don’t exist in this hash set, and adding those values to it with each recursive iteration.

This recursive method can be made generic, and in fact is similar to an FsCheck method called Collect.

public static Gen<IEnumerable<B>> Collect<A, B>(this IEnumerable<A> seq, Func<A, Gen<B>> func) =>
seq.Aggregate(
Gen.Constant(Enumerable.Empty<B>()),
(accumulator, a) =>
accumulator.SelectMany(
_ => func(a),
(enumerable, b) => enumerable.Append(b)));

The method signature shows us that it accepts a collection of some A objects, a function that turns an object in this collection into an FsCheck generator of another type of object B and then finally returns an FsCheck generator of a collection of these B objects.

If we supplied our example concrete type parameters it would be:

Gen<IEnumerable<(int SupplierId, bool OutOfStock)>> Collect<A, B>(this IEnumerable<int> seq, Func<int, Gen<(int SupplierId, bool OutOfStock)>> func)

The method iterates over the input collection, aggregating a generator that returns a collection. The SelectMany/bind operation is used on the generator to get the latest value of the output collection and append a new value to it.

This allows the previous supplier generator to be rewritten as:

Enumerable.Range(0, 20).Collect(
supplierId =>
from outOfStock in Arb.Default.Bool().Generator
select (supplierId, outOfStock))

The result will be something like: [(0, true), (1, false) ... (19, false)]

--

--

Nick Lydon

British software developer working as a freelancer in Berlin. Mainly dotnet, but happy to try new things! https://github.com/NickLydon