Testing code that depends on randomness, like Math.random(), requires isolating the random source behind an interface so it can be mocked and given deterministic values in tests.
Key Points: • Wrap Math.random() (or java.util.Random) inside a small class, e.g. RandomGenerator, that your production code depends on via an interface. • In tests, mock that interface and stub its method with when()/thenReturn() to return a fixed value. • This makes the test deterministic and repeatable instead of relying on chance or wide assertion ranges. • The same pattern applies to any nondeterministic source - system clocks, UUID generation, or external randomness services. • Directly calling Math.random() in business logic without abstraction makes it effectively untestable with precision.
Example: A DiceRoller class calling Math.random() internally is hard to test; extracting a RandomSource.nextDouble() dependency lets a test stub it to return exactly 0.5 and assert the resulting roll is deterministic.
Code Example:
interface RandomSource {
double nextDouble();
}
class DiceRoller {
private final RandomSource random;
DiceRoller(RandomSource random) { this.random = random; }
int roll() { return (int) (random.nextDouble() * 6) + 1; }
}
@Test
void rollsPredictableValue() {
RandomSource random = mock(RandomSource.class);
when(random.nextDouble()).thenReturn(0.0);
assertEquals(1, new DiceRoller(random).roll());
}Interview Tip: A concise interview answer is:
"I abstract the random source behind a small interface, inject it into the class under test, and mock that interface to return a fixed value with when()/thenReturn(). That turns an inherently nondeterministic method into something I can assert on precisely."