How does unittest.mock.mock_open let you test a function that reads a file?
answer
- Fake the file, not the filesystem
- One helper builds the whole handle
- read_data drives every read method
- __enter__ hands back the same handle
- Mode is accepted and ignored
basics
~20 sunittest.mock.mock_open(read_data="...") builds a MagicMock that stands in for the builtin open. Patch open with it, and the handle the code receives serves that text from read, readline, readlines and iteration, without touching a real file.
solid answer
~40 s`mock_open` is a factory that returns a `MagicMock` pre-wired to imitate `open` and the object `open` hands back. You give it `read_data` and patch the name the code under test calls, usually `patch("mypkg.mymod.open", new_callable=mock_open, read_data="a\nb\n")`. Calling that mock returns `m.return_value` — the *handle* — and because `mock_open` configures the handle's `__enter__` to return the handle itself, `with open(path) as f:` binds `f` to it. From there `f.read()`, `f.readline()`, `f.readlines()` and `for line in f` all draw from `read_data`. Every `open` call returns that one shared handle, though the read position is reset on each call to the mock. `mock_open` ignores the mode argument and its `seek`/`tell` are inert auto-created mocks, so it fakes reading, not a filesystem.
code
python · 12 linesfrom unittest.mock import mock_open, patch
def count_lines(path):
with open(path) as f:
return sum(1 for line in f)
m = mock_open(read_data="alpha\nbeta\ngamma\n")
with patch("builtins.open", m):
assert count_lines("/var/log/ingest.log") == 3
m.assert_called_once_with("/var/log/ingest.log")go deeper
Be ready to write the three lines from memory: build the double with read_data, patch open with it, call the function. Know that the handle is a mock, not a real file, and that nothing is written to disk.
Explain the wiring: calling the mock returns its return_value, enter returns that same handle, and read/readline/readlines/iteration all draw from one shared read_data whose position resets on each open call.
Show where the double lies. Mode is ignored, seek and tell are inert, str-versus-bytes mismatches surface deep inside the code, and a test that leans on any of those can pass while proving nothing.
Own the policy question of how much of the I/O boundary a team fakes at all. Argue when a faked handle buys fast, deterministic tests and when it quietly encodes an untrue model of file behaviour into the suite.
`unittest.mock.mock_open` is a small factory in the standard library's mock package. It does not emulate a filesystem: it builds a single `MagicMock` that has been pre-configured to behave like the builtin `open` and like the file object `open` returns, for the handful of methods test code actually exercises. ## The object graph `m = mock_open(read_data="a\nb\n")` gives you a mock you install in place of `open`. When the code under test calls `open(path)`, it receives `m.return_value` — one child mock, conventionally called *the handle*. `mock_open` additionally wires: * `handle.__enter__` to return the handle itself, so `with open(path) as f:` binds `f` to the same object rather than to a fresh anonymous child; * `handle.__exit__` to return `False`, so an exception raised inside the `with` block still propagates instead of being silently swallowed; * `handle.read`, `handle.readline`, `handle.readlines` and `handle.__iter__` / `handle.__next__` to serve `read_data`. That last group is the point of the helper. A bare `MagicMock` would return another `MagicMock` from `read()`, and iterating it would yield nothing at all; `mock_open` gives those methods believable behaviour driven by one string. ## read_data and the read position `read_data` is served consistently across the read methods: `read()` returns everything remaining, `readline()` returns the next line including its `\n`, `readlines()` returns the rest as a list, and iteration yields the same lines one at a time. The position is shared, so a second `read()` inside the same `with` block returns `''`, exactly as a real file would. The position resets on each *call to the mock*. If the code under test opens the same path twice, both opens see the full `read_data` from the beginning — a behaviour added in 3.7.1, and one that makes a two-pass function testable without extra setup. It also means the reset is tied to `open` being called, not to the handle being closed. ## Where you patch `open` is a builtin, so there are two spellings that work: `patch("builtins.open", m)`, which replaces it globally for the duration, and `patch("mypkg.mymod.open", ...)`, which installs the double as a module-level name that shadows the builtin only inside that module. The second is narrower and does not need `create=True`; mock has special-cased builtins in a patched module since 3.5. Either way, `new_callable=mock_open` lets `patch` build the double for you and pass `read_data` straight through as a keyword. ## One handle, one record Every call to the patched `open` returns the same handle object. That is convenient — you always know where to look — but it means the calls made through two different `open` calls land in one record. `m.mock_calls` shows the whole interleaved script: `call(path)`, `call().__enter__()`, `call().read()`, `call().__exit__(None, None, None)`, `call().close()`. If a test needs to distinguish two files, assert on content rather than on which handle produced it, or set `m.side_effect` to hand out distinct handles. ## What it deliberately does not do `mock_open` ignores the mode argument entirely. Opening `"wb"` still serves the `read_data` you configured, and passing a `str` `read_data` to code that expects `bytes` produces a `TypeError` deep inside the code under test rather than at the double. If the code reads binary, pass `bytes`. `seek` and `tell` exist only as auto-created child mocks: calling them returns another mock and does not move the read position. Code whose logic rewinds a file will pass its test while doing nothing. Encoding, newline translation, buffering, `os.PathLike` handling, `errors=` and context-manager-less usage are all outside the double as well, because none of them are implemented — the handle simply records whatever it was called with. ## When it is the right tool `mock_open` is at its best when the file is incidental: the function under test parses or counts something, and you want the parsing logic exercised against a literal string with no I/O in the test. It is a poor fit when the behaviour under test *is* file handling — partial reads, rewinds, encoding errors, concurrent writers — because those are precisely the semantics the double omits. The failure mode to watch for is a green test that proved nothing, because the fake never had the behaviour the assertion assumed.
- If the code under test opens the same path twice in one function, what does the second handle give you?The very same handle object — every call to the patched `open` returns `m.return_value` — but the read position is reset on each call to the mock, so the second `open` serves `read_data` from the beginning again. That reset landed in 3.7.1. Because there is one handle, calls made through both opens appear interleaved in a single `mock_calls` record.
- What happens if the code opens the file in binary mode but read_data is a str?`mock_open` ignores the mode entirely, so `read()` still returns the `str`. Nothing fails at the double; the failure surfaces later inside the code under test, typically a `TypeError` when it concatenates with a `bytes` literal or an `AttributeError` on a missing `decode`. Pass a `bytes` `read_data` when the code reads binary, and let the mismatch fail loudly rather than mid-parse.
- Does a mock_open handle support seek() and tell()?Only as auto-created child mocks. `handle.seek(0)` returns another `MagicMock` and does not move the read position, and `handle.tell()` returns a mock rather than an integer. Code that rewinds and re-reads will therefore find the stream already exhausted, or will compare a mock against an int. If rewind behaviour is the thing under test, `mock_open` is the wrong double.
It is a stage prop book: it opens, it has the page of text you printed for the scene, and the actor can read from it — but there is nothing behind the cover, so any business involving flipping back to chapter one is mime.
saying these in an interview costs you the question
- Believes mock_open writes a real temporary file somewhere
- Expects read_data to honour the mode argument
- Thinks each open() call returns a fresh, separate handle
- Assumes seek(0) rewinds the faked read position
- Says a plain Mock works unchanged in a with-block