Course contentsShow
← C#
Lesson 1647 of 1,90815. Software Quality and TestingFree lesson

Stubbing Randomness

Replace non-deterministic random providers with predictable data sources.

In this lesson, you will learn how to make non-deterministic code predictable by replacing Random with a controlled, stubbed interface.

The Problem with Non-Determinism

When your production code relies on System.Random, your tests become "flaky." If you are testing a feature that triggers a "Critical Hit" in a game based on a random roll, or generates a temporary filename, the outcome changes every time you run the test. A test that passes once might fail the next time because the random number generator happened to pick an unfavorable value. This makes debugging impossible and trust in your test suite disappear.

The Solution: Dependency Injection

To fix this, you must stop instantiating Random directly inside your business logic. Instead, abstract the concept of randomness behind an interface.

public interface IRandomProvider
{
    int Next(int min, int max);
}

By injecting this interface into your service, you decouple your logic from the implementation of randomness. In production, you provide a wrapper around System.Random. In your unit tests, you use a Stub.

Implementing the Stub

Because you are using tools like Moq or NSubstitute (which we covered in previous lessons), you can force the "random" provider to return the exact sequence of numbers required to test specific code paths:

[Test]
public void CalculateBonus_WhenRollIsHigh_AppliesMultiplier()
{
    // Arrange
    var stubRandom = new Mock<IRandomProvider>();
    // Force the "random" value to be 95
    stubRandom.Setup(r => r.Next(1, 100)).Returns(95);
    
    var service = new GameEngine(stubRandom.Object);

    // Act
    var result = service.CalculateBonus();

    // Assert
    Assert.AreEqual(2.0, result.Multiplier);
}

By stubbing the random provider, you gain full control over the edge cases. You can test what happens when the random value is the minimum (1), the maximum (100), or exactly at the threshold of a business rule. The logic remains exactly the same as your production code, but the behavior becomes deterministic and repeatable.

Key Takeaway: Never use concrete Random instances in logic you need to test; hide them behind an interface so you can force specific outputs to verify your code's behavior under every possible scenario.