↓ Skip to main content
  1. Writing/

A time on the Phoenix scope goes stale in a LiveView

··

Phoenix 1.8’s phx.gen.auth builds a Scope struct for each request, and it goes first to almost every context function. So the scope looks like a good home for the current time: one now per request, which a test can set.

That holds in a controller. A LiveView’s on_mount hook assigns the scope once, at mount, and the process lives as long as the browser tab is open. Leave the tab open for an hour, and a 15-minute token checked against that now still reads as fresh.

So in our app the time is a now argument. A public function defaults it to DateTime.utc_now() in its head, so a real call reads the clock and a test can pass any time:

def expired?(%Token{inserted_at: at}, now \\ DateTime.utc_now()) do
  DateTime.before?(at, DateTime.shift(now, minute: -15))
end

at = ~U[2026-01-02 03:04:05Z]
assert Tokens.expired?(%Token{inserted_at: at}, DateTime.shift(at, minute: 16))

A custom Credo check flags any other clock read in lib/, Ecto’s ago/2 included. So a private function takes now as a plain argument, and a query compares with ^now.

Glenn Roberts
Written by
Glenn Roberts
Hands-on engineer and former CTO. I help companies build with AI.