skip to content

Why can't unittest.mock.patch replace datetime.datetime.now directly?

level: middleimportance: should knowfreq 45%

answer

  1. patch works by setting an attribute
  2. Some types refuse attribute assignment
  3. datetime.datetime is implemented in C
  4. Modules are writable, static types are not
  5. Subclass it, or inject a now() callable

basics

~10 s

datetime.datetime is a C-implemented type whose attributes cannot be assigned, so patch's setattr raises TypeError. Inject a now() callable instead, or rebind the module-level name to a pure-Python subclass that overrides now.

solid answer

~40 s

`unittest.mock.patch` works by remembering an attribute, calling `setattr` to install a replacement, and setting it back on exit. C-defined types keep their methods in a static structure and refuse attribute assignment, so `patch.object(datetime.datetime, "now")` raises `TypeError` — the same reason you cannot reassign `str.upper`. The distinction that resolves it: the `datetime` *module* is an ordinary writable object, so patching a name on a module succeeds; patching an attribute of the type does not. Preferred fix is a seam — `def stamp(event, now=datetime.datetime.now)` — which needs no patching at all. Otherwise define a pure-Python subclass of `datetime.datetime` with a frozen `now` classmethod and rebind the name the module under test imported; `isinstance` checks still pass. Freeze an aware value via `datetime.timezone.utc`; `datetime.datetime.utcnow()` has been deprecated since 3.12.

code

python · 16 lines
python
import datetime

try:
    datetime.datetime.now = lambda tz=None: None
except TypeError as exc:
    print("refused:", type(exc).__name__)


class FrozenClock(datetime.datetime):
    @classmethod
    def now(cls, tz=None):
        return cls(2026, 9, 3, 12, 0, tzinfo=tz or datetime.timezone.utc)


print(FrozenClock.now().isoformat())
print(isinstance(FrozenClock.now(), datetime.datetime))

go deeper

for a junior

Recognise the failure rather than fight it: assigning to a method of a built-in type such as datetime.datetime raises TypeError. Know that passing the current time in as an argument sidesteps the whole problem.

for a middle

Explain that patch is just setattr plus restore, that C-defined types refuse attribute assignment while modules and pure-Python classes accept it, and how a subclass with a frozen now classmethod fills the gap.

for a senior

Demonstrate the judgment: prefer an injected clock or a single project-level helper over scattered patches, always freeze an aware value, and keep the wall clock and the monotonic clock faked independently.

for a principal

Own the reading of the error as a design signal — a codebase that needs twenty clock patches has no time seam — and set the convention for how time enters the system and how it is frozen in tests.

### How patch does its work `unittest.mock.patch` is not magic. It resolves the target string down to an object, remembers the current value of the named attribute, calls `setattr` to install the replacement, and calls `setattr` again on exit to restore the original. Everything it can do follows from that: if the attribute cannot be set, the patch cannot happen. ### Why the type refuses `datetime.datetime` is implemented in C. A C-defined type's methods live in a static structure built when the extension module initialises, and CPython refuses attribute assignment on such types — `datetime.datetime.now = something` raises `TypeError`, and so does `unittest.mock.patch.object(datetime.datetime, "now")`, which is the same assignment wearing a context manager. This is not specific to `datetime`; `str.upper`, `int.__add__` and `list.append` are equally unassignable. Pure-Python classes have a writable class dictionary, which is why the same technique works everywhere else and produces a surprising, seemingly arbitrary failure here. Note the distinction that resolves most of the confusion: the `datetime` *module* is an ordinary module object and its attributes **are** writable, so `patch("datetime.datetime", Replacement)` succeeds — it rebinds a name on the module — while `patch.object(datetime.datetime, "now")` fails, because it tries to write on the type itself. ### Route one: inject the callable (preferred) Give the function a `now` parameter defaulting to the real thing: ```python import datetime def stamp(event, now=datetime.datetime.now): return f"{event}@{now(datetime.timezone.utc).isoformat()}" ``` The test passes `lambda tz: fixed`. No patching, no type gymnastics, and the dependency is visible in the signature. When many call sites need it, hoist a single project-level `utcnow()` helper that everything calls; then there is exactly one seam to inject or, at worst, one pure-Python function to patch. ### Route two: substitute a subclass When the code is already written against `datetime.datetime.now()`, define a pure-Python subclass with the classmethod you want and rebind the module-global name that the code under test imported: ```python class FrozenClock(datetime.datetime): @classmethod def now(cls, tz=None): return cls(2026, 9, 3, 12, 0, tzinfo=tz or datetime.timezone.utc) ``` Because `FrozenClock` subclasses `datetime.datetime`, any `isinstance` check, comparison or arithmetic in the production code keeps working. What you patch depends on how the consumer imported the name — a module that did `from datetime import datetime` has its own global to rebind, one that did `import datetime` reaches the class through the module object. ### Freeze an *aware* time Whichever route you take, return a timezone-aware value: `datetime.datetime.now(datetime.timezone.utc)`. A frozen naive datetime reintroduces exactly the environment dependence you were removing, because `datetime.datetime.now()` with no argument uses the machine's local timezone, so the same test can format a different string on a laptop and on a build agent. `datetime.datetime.utcnow()` is deprecated as of 3.12 precisely because it returns a naive value that *looks* like UTC; new code should not use it, frozen or otherwise. ### Do not fuse the two clocks The wall clock and the monotonic clock answer different questions, and a test should fake them separately. Deriving a frozen `time.monotonic()` from a frozen `datetime` value encodes a relationship that does not exist in production — the monotonic clock's zero point is undefined and unrelated to the calendar — and it will hide a bug where the code mixes the two, which is the very mistake worth catching. ### The smell behind the question Needing to freeze the clock in twenty tests, each with its own patch, is the code telling you the current time is a hard-wired global. One injected clock seam replaces all twenty patches and removes the failure mode entirely. The interview value of this question is not the `TypeError` trivia; it is whether the candidate reads the error as "mock is broken" or as "this dependency has no seam".

  • Why does substituting a subclass of datetime.datetime keep the production code working?
    Because every check the production code makes still holds: `isinstance` against `datetime.datetime` passes, comparisons and arithmetic use the inherited implementations, and formatting is unchanged. You are only overriding the one classmethod that reads the clock. A `Mock` in the same position would break the first comparison or subtraction it met, which is why a subclass is the right shape of double here.
  • Why insist that a frozen clock returns a timezone-aware datetime?
    `datetime.datetime.now()` with no argument returns a naive value in the machine's local timezone, so a test that freezes it can still format differently on a laptop and a build agent — you removed one environment dependence and kept another. Freezing `datetime.datetime.now(datetime.timezone.utc)` pins both. It is also why `datetime.datetime.utcnow()` was deprecated in 3.12: it returns a naive value that merely looks like UTC.
  • Should the frozen wall clock and a frozen monotonic clock be derived from one another?
    No. They answer different questions: the monotonic clock's zero point is undefined and unrelated to the calendar, so deriving one from the other encodes a relationship production does not have. Fake them separately. Fusing them also hides exactly the bug worth catching — code that measures a duration with the wall clock, or stamps a record with a monotonic reading.

saying these in an interview costs you the question

  • Concludes mock cannot patch anything in the standard library
  • Assigns to datetime.datetime.now and expects it to stick
  • Replaces the datetime class with a bare Mock
  • Returns a naive datetime from the frozen clock
  • Reaches for datetime.datetime.utcnow() in new code
  • Patches the clock in twenty tests instead of adding one seam

context