Inject Time And Randomness

inject-time-and-randomness

Summary

Direct reads of clocks and random sources make behavior hard to reproduce, test, and explain. Pass time and randomness through explicit seams at the boundary where nondeterminism is chosen.

Problem

Time and randomness make code harder to test when they are read from inside the behavior being checked. The caller cannot reproduce the boundary case, and the test must race the clock or accept whatever random value appears.

Preferred Move

Inject time, identifiers, random choices, and nonce-like values at the boundary where nondeterminism is acceptable. Keep deterministic decision logic behind explicit parameters or a small trait only when the operation needs a reusable source.

Tradeoff

Do not introduce a broad clock or random-provider trait for one local value. Passing a timestamp, seed, generated ID, or chosen value is often clearer than adding a mockable service.

Agent Instruction

When adding behavior that reads time or randomness, keep nondeterminism at the edge. Pass explicit
values into the decision logic, or introduce a narrow provider only when multiple operations share
the same source.

Examples

Bad: the decision reads the clock internally, so tests cannot choose the boundary instant.

pub fn is_expired(deadline: SystemTime) -> bool {
    SystemTime::now() >= deadline
}

Good: the caller supplies the instant used by the decision.

pub fn is_expired(deadline: SystemTime, now: SystemTime) -> bool {
    now >= deadline
}

References

Source Use Note
SystemTime adapts Wall-clock time can move in ways callers must handle.
C-CALLER-CONTROL adapts Callers should keep meaningful control over inputs.