skip to content

mock_open and Magic Methods

Faking file reads with mock_open and configuring the dunder methods MagicMock supports, so with-blocks and iteration work on a double. The classic 'how do you test code that opens a file' question.

part ofPythonoverview, primer and where to startread it →
on this pageshow

questions

4

How does unittest.mock.mock_open let you test a function that reads a file?

level: juniorimportance: must knowfreq 60%

answer

  1. Fake the file, not the filesystem
  2. One helper builds the whole handle
  3. read_data drives every read method
  4. __enter__ hands back the same handle
  5. Mode is accepted and ignored

basics

~20 s

unittest.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 lines
python
from 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

for a junior

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.

for a middle

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.

for a senior

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.

for a principal

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

context

open as a page

How do you assert what your code wrote through a mock_open file handle?

level: middleimportance: must knowfreq 50%

basics

~10 s

Reach the handle through the mock's return_value, then assert on its write child: assert_called_once_with for a single write, or join the first argument of every call in handle.write.call_args_list to rebuild the whole document.

open as a page

How does unittest.mock.PropertyMock fake an attribute that is backed by a property?

level: middleimportance: should knowfreq 30%

basics

~20 s

PropertyMock implements the descriptor hooks, so it only works when attached to the class rather than to an instance. Reading the attribute calls it with no arguments; assigning to it calls it with the new value.

open as a page

What do len() and iteration return on a MagicMock by default, and why is that dangerous in a test?

level: seniorimportance: should knowfreq 35%

basics

~20 s

A MagicMock is length zero and iterates as empty, while still being truthy. So a loop over one never executes and a test asserting on its results can pass while proving nothing — the classic vacuous green.

open as a page