[{"data":1,"prerenderedAt":1085},["ShallowReactive",2],{"page-\u002Fadvanced-mocking-test-doubles-in-python\u002Fdependency-injection-for-testability\u002Finjecting-a-clock-instead-of-patching-datetime\u002F":3},{"id":4,"title":5,"body":6,"description":1048,"extension":1049,"meta":1050,"navigation":123,"path":1081,"seo":1082,"stem":1083,"__hash__":1084},"content\u002Fadvanced-mocking-test-doubles-in-python\u002Fdependency-injection-for-testability\u002Finjecting-a-clock-instead-of-patching-datetime\u002Findex.md","Injecting a Clock Instead of Patching datetime",{"type":7,"value":8,"toc":1038},"minimark",[9,30,33,51,56,85,89,299,394,530,534,548,551,555,617,621,624,627,688,696,780,784,790,864,871,882,889,961,965,976,986,998,1002,1029,1034],[10,11,12,13,17,18,21,22,25,26,29],"p",{},"Code that calls ",[14,15,16],"code",{},"datetime.now()"," directly is code whose tests must either wait for time to pass or reach into the standard library and replace it. Both are bad trades: waiting makes tests slow and flaky, and patching ",[14,19,20],{},"datetime"," — directly or through ",[14,23,24],{},"freezegun"," — changes the clock for everything in the process, including logging, database drivers and anything else that happens to ask what time it is. An injected clock replaces both with something simpler: the code asks a ",[14,27,28],{},"clock"," object for the time, production passes the real one, and tests pass one they control.",[10,31,32],{},"The change is small in the code and large in the tests. Expiry, scheduling, rate limiting, token lifetimes and retry backoff all become testable in microseconds with assertions that read like the specification: advance an hour and one second, and the token is no longer valid.",[10,34,35,36,38,39,42,43,46,47,50],{},"The cost is modest and mostly one-off. The protocol and both implementations are thirty lines. Each service that reads time gains a constructor parameter with a default, so existing callers are unaffected. The ongoing discipline is simply that new code asks its clock rather than calling ",[14,37,16],{}," directly — something a lint rule banning bare ",[14,40,41],{},"datetime.now"," and ",[14,44,45],{},"time.time"," outside the ",[14,48,49],{},"SystemClock"," class can enforce mechanically, so the pattern does not erode as new code is added.",[52,53,55],"h2",{"id":54},"prerequisites","Prerequisites",[57,58,59,71,77],"ul",{},[60,61,62,63,66,67,70],"li",{},"Python 3.9+ for ",[14,64,65],{},"zoneinfo","; ",[14,68,69],{},"typing.Protocol"," for the interface.",[60,72,73,76],{},[14,74,75],{},"pytest >= 8.0",".",[60,78,79,80,76],{},"The injection patterns in ",[81,82,84],"a",{"href":83},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fdependency-injection-for-testability\u002F","dependency injection for testability",[52,86,88],{"id":87},"solution","Solution",[90,91,96],"pre",{"className":92,"code":93,"language":94,"meta":95,"style":95},"language-python shiki shiki-themes github-light github-dark","import time\nfrom datetime import datetime, timedelta, timezone\nfrom typing import Protocol\n\n\nclass Clock(Protocol):\n    def now(self) -> datetime: ...          # aware, UTC\n    def monotonic(self) -> float: ...       # for durations and deadlines\n\n\nclass SystemClock:\n    def now(self) -> datetime:\n        return datetime.now(timezone.utc)\n\n    def monotonic(self) -> float:\n        return time.monotonic()\n\n\nclass FakeClock:\n    \"\"\"A clock the test drives explicitly.\"\"\"\n\n    def __init__(self, start: datetime = datetime(2026, 1, 1, tzinfo=timezone.utc)):\n        self._now = start\n        self._mono = 1_000.0\n\n    def now(self) -> datetime:\n        return self._now\n\n    def monotonic(self) -> float:\n        return self._mono\n\n    def advance(self, **delta) -> None:\n        step = timedelta(**delta)\n        self._now += step\n        self._mono += step.total_seconds()   # both clocks move together\n","python","",[14,97,98,106,112,118,125,130,136,142,148,153,158,164,170,176,181,187,193,198,203,209,215,220,226,232,238,243,248,254,259,264,270,275,281,287,293],{"__ignoreMap":95},[99,100,103],"span",{"class":101,"line":102},"line",1,[99,104,105],{},"import time\n",[99,107,109],{"class":101,"line":108},2,[99,110,111],{},"from datetime import datetime, timedelta, timezone\n",[99,113,115],{"class":101,"line":114},3,[99,116,117],{},"from typing import Protocol\n",[99,119,121],{"class":101,"line":120},4,[99,122,124],{"emptyLinePlaceholder":123},true,"\n",[99,126,128],{"class":101,"line":127},5,[99,129,124],{"emptyLinePlaceholder":123},[99,131,133],{"class":101,"line":132},6,[99,134,135],{},"class Clock(Protocol):\n",[99,137,139],{"class":101,"line":138},7,[99,140,141],{},"    def now(self) -> datetime: ...          # aware, UTC\n",[99,143,145],{"class":101,"line":144},8,[99,146,147],{},"    def monotonic(self) -> float: ...       # for durations and deadlines\n",[99,149,151],{"class":101,"line":150},9,[99,152,124],{"emptyLinePlaceholder":123},[99,154,156],{"class":101,"line":155},10,[99,157,124],{"emptyLinePlaceholder":123},[99,159,161],{"class":101,"line":160},11,[99,162,163],{},"class SystemClock:\n",[99,165,167],{"class":101,"line":166},12,[99,168,169],{},"    def now(self) -> datetime:\n",[99,171,173],{"class":101,"line":172},13,[99,174,175],{},"        return datetime.now(timezone.utc)\n",[99,177,179],{"class":101,"line":178},14,[99,180,124],{"emptyLinePlaceholder":123},[99,182,184],{"class":101,"line":183},15,[99,185,186],{},"    def monotonic(self) -> float:\n",[99,188,190],{"class":101,"line":189},16,[99,191,192],{},"        return time.monotonic()\n",[99,194,196],{"class":101,"line":195},17,[99,197,124],{"emptyLinePlaceholder":123},[99,199,201],{"class":101,"line":200},18,[99,202,124],{"emptyLinePlaceholder":123},[99,204,206],{"class":101,"line":205},19,[99,207,208],{},"class FakeClock:\n",[99,210,212],{"class":101,"line":211},20,[99,213,214],{},"    \"\"\"A clock the test drives explicitly.\"\"\"\n",[99,216,218],{"class":101,"line":217},21,[99,219,124],{"emptyLinePlaceholder":123},[99,221,223],{"class":101,"line":222},22,[99,224,225],{},"    def __init__(self, start: datetime = datetime(2026, 1, 1, tzinfo=timezone.utc)):\n",[99,227,229],{"class":101,"line":228},23,[99,230,231],{},"        self._now = start\n",[99,233,235],{"class":101,"line":234},24,[99,236,237],{},"        self._mono = 1_000.0\n",[99,239,241],{"class":101,"line":240},25,[99,242,124],{"emptyLinePlaceholder":123},[99,244,246],{"class":101,"line":245},26,[99,247,169],{},[99,249,251],{"class":101,"line":250},27,[99,252,253],{},"        return self._now\n",[99,255,257],{"class":101,"line":256},28,[99,258,124],{"emptyLinePlaceholder":123},[99,260,262],{"class":101,"line":261},29,[99,263,186],{},[99,265,267],{"class":101,"line":266},30,[99,268,269],{},"        return self._mono\n",[99,271,273],{"class":101,"line":272},31,[99,274,124],{"emptyLinePlaceholder":123},[99,276,278],{"class":101,"line":277},32,[99,279,280],{},"    def advance(self, **delta) -> None:\n",[99,282,284],{"class":101,"line":283},33,[99,285,286],{},"        step = timedelta(**delta)\n",[99,288,290],{"class":101,"line":289},34,[99,291,292],{},"        self._now += step\n",[99,294,296],{"class":101,"line":295},35,[99,297,298],{},"        self._mono += step.total_seconds()   # both clocks move together\n",[90,300,302],{"className":92,"code":301,"language":94,"meta":95,"style":95},"class TokenService:\n    def __init__(self, clock: Clock = SystemClock()) -> None:   # default: real time\n        self._clock = clock\n\n    def issue(self, ttl: timedelta) -> \"Token\":\n        return Token(expires_at=self._clock.now() + ttl)\n\n    def is_valid(self, token: \"Token\") -> bool:\n        return self._clock.now() \u003C token.expires_at\n\n\ndef test_token_expires_after_its_ttl():\n    clock = FakeClock()\n    service = TokenService(clock)\n    token = service.issue(ttl=timedelta(hours=1))\n\n    assert service.is_valid(token)\n    clock.advance(hours=1, seconds=1)          # explicit, instant\n    assert not service.is_valid(token)\n",[14,303,304,309,314,319,323,328,333,337,342,347,351,355,360,365,370,375,379,384,389],{"__ignoreMap":95},[99,305,306],{"class":101,"line":102},[99,307,308],{},"class TokenService:\n",[99,310,311],{"class":101,"line":108},[99,312,313],{},"    def __init__(self, clock: Clock = SystemClock()) -> None:   # default: real time\n",[99,315,316],{"class":101,"line":114},[99,317,318],{},"        self._clock = clock\n",[99,320,321],{"class":101,"line":120},[99,322,124],{"emptyLinePlaceholder":123},[99,324,325],{"class":101,"line":127},[99,326,327],{},"    def issue(self, ttl: timedelta) -> \"Token\":\n",[99,329,330],{"class":101,"line":132},[99,331,332],{},"        return Token(expires_at=self._clock.now() + ttl)\n",[99,334,335],{"class":101,"line":138},[99,336,124],{"emptyLinePlaceholder":123},[99,338,339],{"class":101,"line":144},[99,340,341],{},"    def is_valid(self, token: \"Token\") -> bool:\n",[99,343,344],{"class":101,"line":150},[99,345,346],{},"        return self._clock.now() \u003C token.expires_at\n",[99,348,349],{"class":101,"line":155},[99,350,124],{"emptyLinePlaceholder":123},[99,352,353],{"class":101,"line":160},[99,354,124],{"emptyLinePlaceholder":123},[99,356,357],{"class":101,"line":166},[99,358,359],{},"def test_token_expires_after_its_ttl():\n",[99,361,362],{"class":101,"line":172},[99,363,364],{},"    clock = FakeClock()\n",[99,366,367],{"class":101,"line":178},[99,368,369],{},"    service = TokenService(clock)\n",[99,371,372],{"class":101,"line":183},[99,373,374],{},"    token = service.issue(ttl=timedelta(hours=1))\n",[99,376,377],{"class":101,"line":189},[99,378,124],{"emptyLinePlaceholder":123},[99,380,381],{"class":101,"line":195},[99,382,383],{},"    assert service.is_valid(token)\n",[99,385,386],{"class":101,"line":200},[99,387,388],{},"    clock.advance(hours=1, seconds=1)          # explicit, instant\n",[99,390,391],{"class":101,"line":205},[99,392,393],{},"    assert not service.is_valid(token)\n",[395,396,399,526],"figure",{"className":397},[398],"diagram",[400,401,408,409,408,413,408,417,408,425,408,435,408,445,408,451,408,461,408,467,408,470,408,474,408,477,408,481,408,483,408,486,408,491,408,496,408,500,408,503,408,506,408,511,408,514,408,516,408,518,408,520,408,522],"svg",{"viewBox":402,"role":403,"ariaLabelledBy":404,"xmlns":407},"0 0 820 262","img",[405,406],"clk-t","clk-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[410,411,412],"title",{"id":405},"Patched datetime versus an injected clock",[414,415,416],"desc",{"id":406},"Two approaches. Patching datetime replaces the clock for the whole process, so logging, database drivers and third-party libraries all see the fake time. An injected clock is passed only to the service under test, so only that service sees controlled time and everything else keeps the real clock.",[418,419],"rect",{"x":420,"y":420,"width":421,"height":422,"rx":423,"fill":424},"0","820","262","14","#fffdf8",[426,427,434],"text",{"x":428,"y":429,"textAnchor":430,"fontSize":431,"fontWeight":432,"fill":433},"410","28","middle","16","700","#3d405b","Who sees the fake time",[418,436],{"x":437,"y":438,"width":439,"height":440,"rx":441,"fill":442,"stroke":443,"strokeWidth":444},"26","52","368","186","12","#fbe9e3","#e07a5f","2",[426,446,450],{"x":447,"y":448,"textAnchor":430,"fontSize":449,"fontWeight":432,"fill":433},"210","78","12.5","patch datetime \u002F freezegun",[418,452],{"x":453,"y":454,"width":455,"height":456,"rx":457,"fill":458,"stroke":459,"strokeWidth":460},"46","94","150","34","8","#f7f0da","#f2cc8f","1.6",[426,462,466],{"x":463,"y":464,"textAnchor":430,"fontSize":465,"fill":433},"121","116","11","TokenService",[418,468],{"x":469,"y":454,"width":455,"height":456,"rx":457,"fill":458,"stroke":459,"strokeWidth":460},"220",[426,471,473],{"x":472,"y":464,"textAnchor":430,"fontSize":465,"fill":433},"295","logging",[418,475],{"x":453,"y":476,"width":455,"height":456,"rx":457,"fill":458,"stroke":459,"strokeWidth":460},"138",[426,478,480],{"x":463,"y":479,"textAnchor":430,"fontSize":465,"fill":433},"160","DB driver",[418,482],{"x":469,"y":476,"width":455,"height":456,"rx":457,"fill":458,"stroke":459,"strokeWidth":460},[426,484,485],{"x":472,"y":479,"textAnchor":430,"fontSize":465,"fill":433},"TLS checks",[426,487,490],{"x":447,"y":488,"textAnchor":430,"fontSize":465,"fill":489},"212","#8f3d22","everything in the process is frozen",[418,492],{"x":493,"y":438,"width":439,"height":440,"rx":441,"fill":494,"stroke":495,"strokeWidth":444},"426","#e6f0ea","#81b29a",[426,497,499],{"x":498,"y":448,"textAnchor":430,"fontSize":449,"fontWeight":432,"fill":433},"610","injected FakeClock",[418,501],{"x":502,"y":454,"width":455,"height":456,"rx":457,"fill":458,"stroke":459,"strokeWidth":460},"446",[426,504,466],{"x":505,"y":464,"textAnchor":430,"fontSize":465,"fill":433},"521",[418,507],{"x":508,"y":454,"width":455,"height":456,"rx":457,"fill":424,"stroke":509,"strokeWidth":510},"620","rgba(61,64,91,0.35)","1.5",[426,512,473],{"x":513,"y":464,"textAnchor":430,"fontSize":465,"fill":433},"695",[418,515],{"x":502,"y":476,"width":455,"height":456,"rx":457,"fill":424,"stroke":509,"strokeWidth":510},[426,517,480],{"x":505,"y":479,"textAnchor":430,"fontSize":465,"fill":433},[418,519],{"x":508,"y":476,"width":455,"height":456,"rx":457,"fill":424,"stroke":509,"strokeWidth":510},[426,521,485],{"x":513,"y":479,"textAnchor":430,"fontSize":465,"fill":433},[426,523,525],{"x":498,"y":488,"textAnchor":430,"fontSize":465,"fill":524},"#2a5f49","only the code under test is controlled",[527,528,529],"figcaption",{},"The gold boxes see fake time. On the left that includes infrastructure the test never meant to touch; on the right it is exactly the service under test.",[52,531,533],{"id":532},"why-this-works","Why this works",[10,535,536,537,539,540,543,544,547],{},"The service no longer knows where time comes from; it asks its clock. In production the default ",[14,538,49],{}," returns real time, so no call site changes. In tests the ",[14,541,542],{},"FakeClock"," returns whatever the test has set, and ",[14,545,546],{},"advance"," moves it forward instantly by exactly the amount the test specifies. The assertion then states the requirement directly — valid before the TTL, invalid one second after — with no reliance on how long the test takes to run.",[10,549,550],{},"Because only the service holds the fake clock, nothing else in the process is affected. Log records carry real timestamps, database drivers see real time for their own timeouts, and TLS certificate validation does not suddenly fail because the process believes it is 2020. That isolation is the main practical advantage over process-wide patching. It also makes the test's intent visible: the reader sees the clock being constructed and advanced, instead of having to know that a decorator three lines up has replaced a standard-library class for the duration of the function.",[52,552,554],{"id":553},"edge-cases-and-failure-modes","Edge cases and failure modes",[57,556,557,568,577,591,605],{},[60,558,559,563,564,567],{},[560,561,562],"strong",{},"Naive datetimes."," A clock returning naive datetimes invites timezone bugs. Always return aware UTC from ",[14,565,566],{},"now()"," and convert to local time at the edges.",[60,569,570,573,574,76],{},[560,571,572],{},"Wall time used for durations."," Wall-clock time can jump backwards under NTP adjustment. Deadlines and elapsed-time measurements should use ",[14,575,576],{},"monotonic()",[60,578,579,582,583,586,587,590],{},[560,580,581],{},"Default argument evaluated once."," ",[14,584,585],{},"def __init__(self, clock=SystemClock())"," shares one instance across all services, which is fine for a stateless clock and wrong for anything with state. Use ",[14,588,589],{},"None"," and construct inside if the clock ever gains state.",[60,592,593,596,597,600,601,604],{},[560,594,595],{},"Code that sleeps."," Advancing a fake clock does not wake a thread blocked in ",[14,598,599],{},"time.sleep",". Inject a sleep function too, or have the fake's ",[14,602,603],{},"sleep"," advance time instead of blocking.",[60,606,607,610,611,613,614,616],{},[560,608,609],{},"Third-party code reading time."," Libraries that call ",[14,612,16],{}," themselves are not affected by the injected clock. That is usually desirable; when it is not, ",[14,615,24],{}," scoped to one test remains an option.",[52,618,620],{"id":619},"testing-calendar-logic-with-a-controlled-clock","Testing calendar logic with a controlled clock",[10,622,623],{},"Expiry is the simplest case. The more valuable one is calendar logic — billing periods, business days, month-end processing, daylight-saving transitions — where the interesting behaviour happens at specific moments that a test running at an arbitrary real time will almost never hit.",[10,625,626],{},"A fake clock lets the test choose those moments deliberately. Start it at 23:59:59 on the last day of a month and advance one second; start it an hour before a daylight-saving transition in the customer's timezone and advance two hours; start it on a Friday evening and advance to Monday morning. Each scenario is a two-line arrangement, and each exercises exactly the boundary where calendar bugs live. Because the start time is an explicit argument, the scenario is also self-documenting: a reader sees immediately that this test is about the last second of March, rather than having to infer it from a frozen-time decorator elsewhere in the file. Scenarios like these belong in every scheduling module's tests.",[90,628,630],{"className":92,"code":629,"language":94,"meta":95,"style":95},"from datetime import datetime, timedelta, timezone\nfrom zoneinfo import ZoneInfo\n\n\ndef test_invoice_is_generated_at_month_end_in_the_customer_zone():\n    tz = ZoneInfo(\"Europe\u002FLondon\")\n    clock = FakeClock(start=datetime(2026, 3, 31, 23, 59, 59, tzinfo=tz).astimezone(timezone.utc))\n    billing = BillingScheduler(clock=clock)\n\n    assert billing.due_invoices() == []\n    clock.advance(seconds=1)                      # crosses into April, local time\n    assert [i.period for i in billing.due_invoices()] == [\"2026-03\"]\n",[14,631,632,636,641,645,649,654,659,664,669,673,678,683],{"__ignoreMap":95},[99,633,634],{"class":101,"line":102},[99,635,111],{},[99,637,638],{"class":101,"line":108},[99,639,640],{},"from zoneinfo import ZoneInfo\n",[99,642,643],{"class":101,"line":114},[99,644,124],{"emptyLinePlaceholder":123},[99,646,647],{"class":101,"line":120},[99,648,124],{"emptyLinePlaceholder":123},[99,650,651],{"class":101,"line":127},[99,652,653],{},"def test_invoice_is_generated_at_month_end_in_the_customer_zone():\n",[99,655,656],{"class":101,"line":132},[99,657,658],{},"    tz = ZoneInfo(\"Europe\u002FLondon\")\n",[99,660,661],{"class":101,"line":138},[99,662,663],{},"    clock = FakeClock(start=datetime(2026, 3, 31, 23, 59, 59, tzinfo=tz).astimezone(timezone.utc))\n",[99,665,666],{"class":101,"line":144},[99,667,668],{},"    billing = BillingScheduler(clock=clock)\n",[99,670,671],{"class":101,"line":150},[99,672,124],{"emptyLinePlaceholder":123},[99,674,675],{"class":101,"line":155},[99,676,677],{},"    assert billing.due_invoices() == []\n",[99,679,680],{"class":101,"line":160},[99,681,682],{},"    clock.advance(seconds=1)                      # crosses into April, local time\n",[99,684,685],{"class":101,"line":166},[99,686,687],{},"    assert [i.period for i in billing.due_invoices()] == [\"2026-03\"]\n",[10,689,690,691,695],{},"The same test written against real time would need to run at precisely that second, in that timezone, to exercise the boundary — which is to say it would never run at all, and the month-end bug would ship. Combined with generated start times from ",[81,692,694],{"href":693},"\u002Fproperty-based-fuzz-testing-strategies\u002Fdesigning-strategies-for-domain-data\u002Fconstraining-dates-and-timezones-in-strategies\u002F","constraining dates and timezones in strategies",", a fake clock turns calendar edge cases from rare accidents into routine test cases.",[395,697,699,777],{"className":698},[398],[400,700,408,705,408,708,408,711,408,715,408,720,408,725,408,729,408,733,408,738,408,741,408,744,408,747,408,750,408,753,408,757,408,761,408,765,408,768,408,771,408,774],{"viewBox":701,"role":403,"ariaLabelledBy":702,"xmlns":407},"0 0 800 236",[703,704],"cal2-t","cal2-d",[410,706,707],{"id":703},"Placing the clock at the boundaries that matter",[414,709,710],{"id":704},"Four calendar boundaries a fake clock can start just before: the last second of a month, the hour before a daylight-saving change, Friday evening before a weekend, and the last second of a year. Advancing across each boundary exercises logic that real-time tests would almost never reach.",[418,712],{"x":420,"y":420,"width":713,"height":714,"rx":423,"fill":424},"800","236",[426,716,719],{"x":717,"y":429,"textAnchor":430,"fontSize":718,"fontWeight":432,"fill":433},"400","15.5","Start just before the boundary, then cross it",[418,721],{"x":722,"y":438,"width":723,"height":724,"rx":465,"fill":458,"stroke":459,"strokeWidth":444},"24","370","76",[426,726,728],{"x":727,"y":448,"fontSize":441,"fontWeight":432,"fill":433},"44","month end",[426,730,732],{"x":727,"y":731,"fontSize":465,"fill":433},"100","31 Mar 23:59:59 → +1 s",[426,734,737],{"x":727,"y":735,"fontSize":465,"fill":736},"118","#8a5a00","billing periods, statements",[418,739],{"x":740,"y":438,"width":723,"height":724,"rx":465,"fill":442,"stroke":443,"strokeWidth":444},"406",[426,742,743],{"x":493,"y":448,"fontSize":441,"fontWeight":432,"fill":433},"daylight-saving change",[426,745,746],{"x":493,"y":731,"fontSize":465,"fill":433},"hour before → +2 h",[426,748,749],{"x":493,"y":735,"fontSize":465,"fill":489},"schedules, duplicated or skipped hours",[418,751],{"x":722,"y":752,"width":723,"height":724,"rx":465,"fill":494,"stroke":495,"strokeWidth":444},"140",[426,754,756],{"x":727,"y":755,"fontSize":441,"fontWeight":432,"fill":433},"166","weekend",[426,758,760],{"x":727,"y":759,"fontSize":465,"fill":433},"188","Friday 18:00 → Monday 09:00",[426,762,764],{"x":727,"y":763,"fontSize":465,"fill":524},"206","business-day calculations",[418,766],{"x":740,"y":752,"width":723,"height":724,"rx":465,"fill":767,"stroke":433,"strokeWidth":460},"#f4f1de",[426,769,770],{"x":493,"y":755,"fontSize":441,"fontWeight":432,"fill":433},"year end",[426,772,773],{"x":493,"y":759,"fontSize":465,"fill":433},"31 Dec 23:59:59 → +1 s",[426,775,776],{"x":493,"y":763,"fontSize":465,"fill":433},"annual limits, ISO week numbers",[527,778,779],{},"Each boundary is a two-line arrangement with a fake clock and practically unreachable with a real one.",[52,781,783],{"id":782},"clocks-that-sleep-as-well-as-tell","Clocks that sleep as well as tell",[10,785,786,787,789],{},"Code that waits — retry loops, pollers, rate limiters — needs to sleep as well as read time, and a fake clock that only answers questions leaves those code paths waiting for real. Giving the clock a ",[14,788,603],{}," method, and having the fake implement it by advancing its own time, makes waiting code instant in tests without changing its logic.",[90,791,793],{"className":92,"code":792,"language":94,"meta":95,"style":95},"class FakeClock:\n    # … now(), monotonic(), advance() as above …\n\n    def sleep(self, seconds: float) -> None:\n        self.slept.append(seconds)              # record what was asked for\n        self.advance(seconds=seconds)           # and pretend it happened\n\n\ndef test_backoff_doubles_between_attempts():\n    clock = FakeClock()\n    clock.slept = []\n    fetch = Mock(side_effect=[TimeoutError, TimeoutError, \"ok\"])\n\n    assert fetch_with_backoff(fetch, clock=clock, base=0.5) == \"ok\"\n    assert clock.slept == [0.5, 1.0]           # the schedule, asserted exactly\n",[14,794,795,799,804,808,813,818,823,827,831,836,840,845,850,854,859],{"__ignoreMap":95},[99,796,797],{"class":101,"line":102},[99,798,208],{},[99,800,801],{"class":101,"line":108},[99,802,803],{},"    # … now(), monotonic(), advance() as above …\n",[99,805,806],{"class":101,"line":114},[99,807,124],{"emptyLinePlaceholder":123},[99,809,810],{"class":101,"line":120},[99,811,812],{},"    def sleep(self, seconds: float) -> None:\n",[99,814,815],{"class":101,"line":127},[99,816,817],{},"        self.slept.append(seconds)              # record what was asked for\n",[99,819,820],{"class":101,"line":132},[99,821,822],{},"        self.advance(seconds=seconds)           # and pretend it happened\n",[99,824,825],{"class":101,"line":138},[99,826,124],{"emptyLinePlaceholder":123},[99,828,829],{"class":101,"line":144},[99,830,124],{"emptyLinePlaceholder":123},[99,832,833],{"class":101,"line":150},[99,834,835],{},"def test_backoff_doubles_between_attempts():\n",[99,837,838],{"class":101,"line":155},[99,839,364],{},[99,841,842],{"class":101,"line":160},[99,843,844],{},"    clock.slept = []\n",[99,846,847],{"class":101,"line":166},[99,848,849],{},"    fetch = Mock(side_effect=[TimeoutError, TimeoutError, \"ok\"])\n",[99,851,852],{"class":101,"line":172},[99,853,124],{"emptyLinePlaceholder":123},[99,855,856],{"class":101,"line":178},[99,857,858],{},"    assert fetch_with_backoff(fetch, clock=clock, base=0.5) == \"ok\"\n",[99,860,861],{"class":101,"line":183},[99,862,863],{},"    assert clock.slept == [0.5, 1.0]           # the schedule, asserted exactly\n",[10,865,866,867,76],{},"The test runs in microseconds and asserts the precise backoff schedule — something a test using real sleeps could only approximate with a timing tolerance. It is the pattern developed further in ",[81,868,870],{"href":869},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fcontrolling-time-and-randomness-in-tests\u002Ftesting-retry-and-backoff-logic-without-waiting\u002F","testing retry and backoff logic without waiting",[10,872,873,874,877,878,881],{},"For async code the same idea applies with an ",[14,875,876],{},"async def sleep"," that advances time and then yields with ",[14,879,880],{},"await asyncio.sleep(0)",", so other tasks still get a turn at each simulated wait.",[10,883,884,885,888],{},"The recorded ",[14,886,887],{},"slept"," list is worth keeping even where no test asserts on it yet. It turns every waiting code path into something observable, and the first time a retry policy is changed by accident — a base delay doubled, a cap removed — a test that asserts the schedule catches it immediately. Without the record, the only symptom would be slower recovery in production, noticed long after the change that caused it.",[395,890,892,958],{"className":891},[398],[400,893,408,897,408,900,408,903,408,905,408,908,408,915,408,920,408,923,408,928,408,932,408,934,408,938,408,942,408,946,408,948,408,951,408,954],{"viewBox":701,"role":403,"ariaLabelledBy":894,"xmlns":407},[895,896],"slp-t","slp-d",[410,898,899],{"id":895},"A fake clock that sleeps by advancing itself",[414,901,902],{"id":896},"The code under test calls clock.sleep with half a second, then one second, between attempts. The fake records each requested duration and advances its own time by that amount instantly. The test asserts the recorded schedule exactly, in microseconds, where real sleeps would take one and a half seconds and need a tolerance.",[418,904],{"x":420,"y":420,"width":713,"height":714,"rx":423,"fill":424},[426,906,907],{"x":717,"y":429,"textAnchor":430,"fontSize":718,"fontWeight":432,"fill":433},"Waiting code, instant tests, exact assertions",[418,909],{"x":437,"y":910,"width":911,"height":912,"rx":913,"fill":442,"stroke":443,"strokeWidth":914},"56","180","60","10","1.8",[426,916,919],{"x":464,"y":917,"textAnchor":430,"fontSize":918,"fill":433},"92","11.5","attempt 1 fails",[418,921],{"x":922,"y":910,"width":479,"height":912,"rx":913,"fill":458,"stroke":459,"strokeWidth":914},"226",[426,924,927],{"x":925,"y":926,"textAnchor":430,"fontSize":918,"fill":433},"306","82","sleep(0.5)",[426,929,931],{"x":925,"y":731,"textAnchor":430,"fontSize":930,"fill":736},"10.5","recorded, advanced",[418,933],{"x":740,"y":910,"width":911,"height":912,"rx":913,"fill":442,"stroke":443,"strokeWidth":914},[426,935,937],{"x":936,"y":917,"textAnchor":430,"fontSize":918,"fill":433},"496","attempt 2 fails",[418,939],{"x":940,"y":910,"width":941,"height":912,"rx":913,"fill":458,"stroke":459,"strokeWidth":914},"606","168",[426,943,945],{"x":944,"y":926,"textAnchor":430,"fontSize":918,"fill":433},"690","sleep(1.0)",[426,947,931],{"x":944,"y":731,"textAnchor":430,"fontSize":930,"fill":736},[418,949],{"x":437,"y":752,"width":950,"height":724,"rx":465,"fill":494,"stroke":495,"strokeWidth":444},"748",[426,952,953],{"x":717,"y":755,"textAnchor":430,"fontSize":441,"fontWeight":432,"fill":433},"assert clock.slept == [0.5, 1.0]",[426,955,957],{"x":717,"y":956,"textAnchor":430,"fontSize":465,"fill":524},"190","exact schedule · microseconds of wall time · no tolerance needed",[527,959,960],{},"Real sleeps would cost a second and a half per run and could only be checked approximately. The recorded schedule is checked exactly.",[52,962,964],{"id":963},"frequently-asked-questions","Frequently Asked Questions",[10,966,967,970,972,973,975],{},[560,968,969],{},"Why not just use freezegun?",[14,971,24],{}," patches ",[14,974,20],{}," process-wide, which affects logging timestamps, database defaults, TLS certificate checks and any library that samples the clock. That is sometimes useful and often surprising. An injected clock affects only the code you pass it to, so a test controls exactly the time it means to control.",[10,977,978,981,982,985],{},[560,979,980],{},"Does injecting a clock mean changing every function signature?","\nOnly at the boundaries where time is read. Services receive a clock in their constructor, and everything below them uses ",[14,983,984],{},"self.clock",". Pure functions that need \"now\" take it as an argument, which also makes them easier to reason about.",[10,987,988,991,992,994,995,997],{},[560,989,990],{},"How do I handle time.monotonic for timeouts and durations?","\nGive the clock a ",[14,993,576],{}," method alongside ",[14,996,566],{},". Durations and deadlines use monotonic time, calendar logic uses wall-clock time, and the fake controls both so tests can advance either one.",[52,999,1001],{"id":1000},"related","Related",[57,1003,1004,1010,1017,1023],{},[60,1005,1006,1009],{},[81,1007,1008],{"href":83},"Dependency Injection for Testability"," — the broader pattern the clock is one instance of.",[60,1011,1012,1016],{},[81,1013,1015],{"href":1014},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fcontrolling-time-and-randomness-in-tests\u002Ffreezing-time-with-freezegun-vs-monkeypatch\u002F","Freezing Time with freezegun vs monkeypatch"," — the patching alternatives and their trade-offs.",[60,1018,1019,1022],{},[81,1020,1021],{"href":869},"Testing Retry and Backoff Logic Without Waiting"," — the sleeping fake clock in full.",[60,1024,1025,1028],{},[81,1026,1027],{"href":693},"Constraining Dates and Timezones in Strategies"," — generating the start times a FakeClock is seeded with.",[10,1030,1031,1032],{},"← Back to ",[81,1033,1008],{"href":83},[1035,1036,1037],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":95,"searchDepth":108,"depth":108,"links":1039},[1040,1041,1042,1043,1044,1045,1046,1047],{"id":54,"depth":108,"text":55},{"id":87,"depth":108,"text":88},{"id":532,"depth":108,"text":533},{"id":553,"depth":108,"text":554},{"id":619,"depth":108,"text":620},{"id":782,"depth":108,"text":783},{"id":963,"depth":108,"text":964},{"id":1000,"depth":108,"text":1001},"Replace datetime patching with an injected clock: a Clock protocol, a controllable fake, timezone-aware defaults, and tests that advance time explicitly without freezegun.","md",{"slug":1051,"type":1052,"breadcrumb":1053,"datePublished":1054,"dateModified":1054,"faq":1055,"howto":1062},"injecting-a-clock-instead-of-patching-datetime","article","Injected Clock","2026-09-18",[1056,1058,1060],{"q":969,"a":1057},"freezegun patches datetime process-wide, which affects logging timestamps, database defaults, TLS certificate checks and any library that samples the clock. That is sometimes useful and often surprising. An injected clock affects only the code you pass it to, so a test controls exactly the time it means to control.",{"q":980,"a":1059},"Only at the boundaries where time is read. Services receive a clock in their constructor, and everything below them uses self.clock. Pure functions that need 'now' take it as an argument, which also makes them easier to reason about.",{"q":990,"a":1061},"Give the clock a monotonic() method alongside now(). Durations and deadlines use monotonic time, calendar logic uses wall-clock time, and the fake controls both so tests can advance either one.",{"name":1063,"description":1064,"steps":1065},"How to inject a clock instead of patching datetime","Define a Clock protocol, give services a clock at construction, provide a controllable fake, and advance time explicitly in tests.",[1066,1069,1072,1075,1078],{"name":1067,"text":1068},"Define the protocol","Declare a Clock with now() returning an aware datetime and monotonic() returning a float.",{"name":1070,"text":1071},"Provide the real implementation","Implement SystemClock using datetime.now(timezone.utc) and time.monotonic.",{"name":1073,"text":1074},"Inject it at the boundary","Give services a clock parameter defaulting to SystemClock so production code needs no changes at call sites.",{"name":1076,"text":1077},"Write a controllable fake","Implement FakeClock with an advance() method that moves both wall and monotonic time.",{"name":1079,"text":1080},"Advance time explicitly in tests","Replace every sleep and every frozen-time decorator with clock.advance(...) at the point the test needs time to pass.","\u002Fadvanced-mocking-test-doubles-in-python\u002Fdependency-injection-for-testability\u002Finjecting-a-clock-instead-of-patching-datetime",{"title":5,"description":1048},"advanced-mocking-test-doubles-in-python\u002Fdependency-injection-for-testability\u002Finjecting-a-clock-instead-of-patching-datetime\u002Findex","by0xAWQcDuU4kuTyJtK0Aovr9gvXeC4C5B8lxIZFsNc",1789718767454]