Make time and randomness inputs to your code, not hidden calls. Inject a clock and a random source so tests can fix them, and keep real cryptographic randomness in production. Then expiry, rate limits and token logic can be tested exactly, and failures can be replayed.
Why this matters for security tests
Many security features depend on time: token expiry, lockout windows, rate limits, certificate validity, one time code windows. Testing them by sleeping is slow and flaky. Testing them by hoping the run lands in the right second is worse.
Random values cause the opposite problem. A test that generates a random token and sometimes hits an edge case fails once a month and nobody can reproduce it.
Inject the clock
Give the code a small interface with a method that returns the current time. Production passes the system clock. Tests pass a fake that you can move forward.
class FakeClock:
def __init__(self, now): self.now = now
def time(self): return self.now
def advance(self, seconds): self.now += seconds
def test_token_expires():
clock = FakeClock(1_000)
tokens = TokenService(clock, ttl=60)
t = tokens.issue("u1")
clock.advance(59)
assert tokens.verify(t)
clock.advance(2)
assert not tokens.verify(t)
The test checks both sides of the boundary, which is where off by one errors in expiry live.
Inject randomness, carefully
For logic that uses randomness only to pick among options, pass a seeded generator in tests. For anything that must be unpredictable, such as session ids, reset tokens and keys, keep the cryptographic source in production and assert properties in tests: length, character set, uniqueness across many calls.
Do not make the production code seedable through configuration. A seed knob that exists "for testing" tends to survive into real deployments.
Handle test ordering and shared state
Reset global state between tests, and give each test its own database schema or transaction. The Google Testing Blog lists concurrency and reliance on non-deterministic behavior among root causes of flaky tests, and order dependence and shared state are typical examples.
Log the seed
If you use randomized test data or property tests, print the seed or the failing case when a test fails, so a developer can replay it. Many property testing libraries have a replay feature for this purpose.
Time zones and clock skew
Run a test with a non UTC time zone, and one that crosses a daylight saving change. Add a skew test for signature and token checks: a few seconds of tolerance should pass, and a large drift should fail.
A short checklist
- Every test sets its own clock and does not read the real one.
- No sleeps longer than a few milliseconds.
- Randomized tests print their seed on failure.
- Tests pass in any order and in parallel.
- Time zone and skew cases exist for token and signature checks.
Quick check
Can I just mock the system time globally?
You can, and it works for small cases, but global patches leak between tests and hide the dependency. An injected clock is more explicit and safer.
Sources
Last reviewed: 19 October 2026.