In a current project I have to deal with lots of multithreaded random number generation. The first challenge that I came upon was that standard .NET Random implementation is not thread-safe. When you call its methods from different threads, Random's inner state becomes mangled at some point and then all you get are zeroes. Uniform probability distribution? Yes. Random? Not so much :)

The obvious choice for me was to wrap Random instances in a ThreadLocal container and get and instance for each thread. Container had to be accessed with a static property on a static class. This solution worked flawlessly for some time, but then, as the project grew, it became obvious that some unit tests are in order.

As my friend said recently, there are two instruments of torture in programmers' hell: random numbers and parallelism. In my project I hit a jackpot. So I decided that in order to write simple and manageable tests, I need to exclude the randomness component from random number generation. And so I created a wrapper interface which I can easily mock in my tests.

First of all, I defined a non thread-safe interface and RNG implementation based on standard .NET implementation:

/// <summary>
/// Random number generator interface.
/// This interface is NOT thread-safe as of itself. 
/// Use <see cref="SafeRandom"/> to get thread-safe RNG.
/// </summary>
public interface IRandom
{
    /// <summary>
    /// Returns a non-negative random integer.
    /// </summary>
    /// <returns>A 32-bit signed integer that is greater than or equal to 0 and less than System.Int32.MaxValue.</returns>
    int Next();

    /// <summary>
    /// Returns a non-negative random integer that is less than the specified maximum.
    /// </summary>
    /// <param name="maxValue">The exclusive upper bound of the random number to be generated. maxValue must be greater than or equal to 0.</param>
    /// <returns>A 32-bit signed integer that is greater than or equal to 0, and less than maxValue; that is, the range of return values ordinarily includes 0 but not maxValue. However, if maxValue equals 0, maxValue is returned.</returns>
    int Next(int maxValue);

    /// <summary>
    /// Returns a random integer that is within a specified range.
    /// </summary>
    /// <param name="minValue">The inclusive lower bound of the random number returned.</param>
    /// <param name="maxValue">The exclusive upper bound of the random number returned. maxValue must be greater than or equal to minValue.</param>
    /// <returns>A 32-bit signed integer greater than or equal to minValue and less than maxValue; that is, the range of return values includes minValue but not maxValue. If minValue equals maxValue, minValue is returned.</returns>
    int Next(int minValue, int maxValue);

    /// <summary>
    /// Returns a random floating-point number that is greater than or equal to 0.0, and less than 1.0.
    /// </summary>
    /// <returns>A double-precision floating point number that is greater than or equal to 0.0, and less than 1.0.</returns>
    double NextDouble();

    /// <summary>
    /// Returns a random floating-point number that is greater than or equal to <see cref="minValue"/>, and less than <see cref="maxValue"/>.
    /// </summary>
    /// <param name="minValue">The inclusive lower bound of the random number returned.</param>
    /// <param name="maxValue">The exclusive upper bound of the random number returned. maxValue must be greater than or equal to minValue.</param>
    /// <returns>A double-precision floating point number that is greater than or equal to <see cref="minValue"/>, and less than <see cref="maxValue"/>.</returns>
    double NextDouble(double minValue, double maxValue);
}
/// <summary>
/// Standard .NET random generator with uniform probability distribution.
/// Random seed is generated using high-quality RNG from <see cref="RNGCryptoServiceProvider "/>
/// </summary>
internal class UniformRandom : IRandom
{
    private static readonly RNGCryptoServiceProvider _global = new RNGCryptoServiceProvider();

    private readonly Random _rnd;

    public UniformRandom()
    {
        byte[] buffer = new byte[4];
        _global.GetBytes(buffer);
        _rnd = new Random(BitConverter.ToInt32(buffer, 0));
    }

    public int Next()
    {
        return _rnd.Next();
    }

    public int Next(int maxValue)
    {
        return _rnd.Next(maxValue);
    }

    public int Next(int minValue, int maxValue)
    {
        return _rnd.Next(minValue, maxValue);
    }

    public double NextDouble()
    {
        return _rnd.NextDouble();
    }

    public double NextDouble(double minValue, double maxValue)
    {
        var r = _rnd.NextDouble() * (maxValue - minValue);
        return minValue + r;
    }
}

Note that in this implementation I use RNGCryptoServiceProvider to generate a random seed. This gives us a very good quality starting point without the performance impact of calling RNGCryptoServiceProvider all the time.

Now we must ensure actual thread safety. Let's create a simple wrapper to get us a different instance of IRandom for each thread:

Note the TestGenerator property. This is how we are going to inject a fake generator inside our unit tests.

Now let's create our fake RNG:

This class allows us to define a sequence of numbers that will appear through a series of calls to any method of IRandom. This gives us an ability to easily design predictable and simple unit tests.

Note that this class is disposable. We always need to use it in a using block so that our test random sequence doesn't affect other tests that maybe don't want to override the RNG.

So let's use our fake generator:

This test checks that a genetic algorithm selector can in fact select the right individual from a population (I know, right? :) ). As you can see, we don't explicitly use RNG in our test. Instead, it is used implicitly in our selector:

As you can see, this selector chooses a random item from the list using a uniform probability distribution. But when we override the RNG in our test, it always chooses an item number 2.

So this is quite simple but powerful technique which can completely remove such an undesirable factor as randomness in unit tests.