Skip to content

← Testing with pytest and Hypothesis step 6 of 19

Easy Primitives

Pass In a Fake Clock

A login session should expire after 30 minutes with no activity. To test that, time has to pass, and a test cannot wait 30 minutes. So Session takes its clock as a parameter. The test passes a fake clock and moves it forward by hand:

clock = FakeClock(start=1_000_000.0)
session = Session(1800, clock=clock)
clock.now += 1800
session.is_expired()  # True, and the test took no time

In production you leave clock out, and the session reads time.monotonic.

What to write

Fill in the three methods of Session(ttl, clock=time.monotonic):

  • __init__ stores ttl and clock. Creating the session counts as activity, so record the time.
  • touch() records activity now. The session then lasts another ttl seconds.
  • is_expired() returns True once at least ttl seconds have passed since the last activity. At exactly ttl seconds, the session has expired.
  • Once the session has expired, touch() does nothing. An expired session stays expired.
  • Read the time only by calling clock(). If you call time.monotonic() yourself, the fake clock has no effect and the tests fail.

clock is any function that takes no arguments and returns a time in seconds, so its type is Callable[[], float]. A FakeClock fits that type, because its __call__ method lets you call it like a function.

ttl is always more than 0.

The fake clock

Keep FakeClock and solve as they are. Calling a FakeClock returns its now attribute, and only the test changes now. Here it starts at 1,000,000 seconds. A real clock such as time.monotonic also starts at an arbitrary number. So measure time as the difference between two readings, never as one reading.

What the driver reports

solve(ttl=..., steps=[...]) creates a session on a fake clock, then runs each step in order:

  • "wait N" moves the fake clock forward N seconds.
  • "touch" calls session.touch().
  • "check" calls session.is_expired().

It returns:

  • expired: what each "check" returned, in order.
  • fake_seconds: how many seconds passed on the fake clock. The tests cover up to an hour and still finish at once.