pytest tmp_path: test file-writing code without touching real files
Use pytest's tmp_path fixture to test a file writer in an isolated directory and assert the saved content.
Example check: Worked and guided code samples are checked against CPython 3.13. Practice uses Pyodide 0.29.5 (Python 3.13) in your browser. How we check lessons
Video lesson: pytest tmp_path: test file-writing code without touching real files
The preview is stored on this site. YouTube loads only when you press Play. Watch on YouTube
Jump to a chapter
Read the video transcript
Select a timestamp to watch that moment on YouTube.
- 00:00
Our function saves a report, and we want to test its exact contents. The first version writes report.txt in the current directory. That sounds harmless until the test runs beside a real report or another test with the same filename. One test may replace the other's input, and a successful result may depend on the machine where it ran. pytest tmp_path solves the destination problem. The fixture gives each test invocation an existing temporary directory represented by a pathlib Path. We pass that directory to our writer, then inspect the file it actually created. I will first show the hard-coded path and its side effect, then inspect what needs
- 00:42
to change in the function boundary. The final run uses pytest itself, not a stand-in, and checks both a two-line report and an empty one. Keep an eye on the difference between asserting that a filename looks right and proving that the saved bytes are right. Both calls returned report.txt, but they refer to the same file in this working directory. The second call overwrote the first result. We also wrote Python's list representation, including brackets and quotes, instead of one name per line. A test that only checked path.name would pass while missing both defects. This is why a file-writing test should inspect the actual text, with an expected
- 01:24
encoding and newline policy. It is also why the destination belongs in the function's inputs. A test can then supply a private folder, while the application can supply its real output folder. No special test-only branch is needed inside the writer. Be careful not to assume pytest automatically changes the process working directory when it supplies tmp_path; it does not. If the function ignores the parameter and still writes Path('report.txt'), the fixture will not protect anything. We need to use the supplied Path in the implementation. This run does not use pytest yet.
- 02:05
It isolates the text-format decision: two names get a newline between them and a final newline, while an empty report has zero characters. We inspect repr so the newline characters are visible. A temporary directory here proves the design can accept a destination. In the actual test, pytest will provide tmp_path instead of us constructing TemporaryDirectory. Its value is a pathlib Path, so the same slash operator builds the child filename. The fixture directory exists already. If the function adds a nested folder, that nested folder may need mkdir. The exact output contract is important because a loose check such as 'Ada is in
- 02:49
the text' would not detect brackets, quotes, or a missing final newline. Before testing a side effect, state precisely what should be on disk. Then the test failure tells us whether the function followed that contract. Now tmp_path appears in the test function signature, which is how pytest knows to inject it. We do not call tmp_path as a function or create it ourselves. Each test receives a separate directory, and the writer receives that directory as an ordinary argument. The test checks the returned Path and reads the saved UTF-8 content. The second test checks the empty-file edge case, so a writer that always appends a
- 03:29
newline would fail. For a normal project, save this code as a test_*.py file and run python -m pytest -q; this captured demonstration calls pytest.main so the four video stages can each run as a single script. The key design is independent of the launcher. Your production code can use the same writer with a real directory. Passing its destination explicitly makes tests reliable without making the implementation aware of pytest. The browser exercise on the lesson page uses TemporaryDirectory to rehearse the contract because its Python console does not inject pytest fixtures.
- 04:12
Both files are named report.txt, but their parent directories differ. The test proves that one write did not overwrite the other and that the returned paths point to the intended places. Notice that we created left and right ourselves; tmp_path only guarantees the parent fixture directory exists. This gives us a reliable pattern for tests that need more than one output file. pytest may retain temporary directories from recent runs for debugging, but each test invocation still gets its own path and should never rely on contents from an earlier run. If you later add CSV output, images, or logs, pass the destination into each
- 04:53
writer and assert the artifacts that matter. For a function that returns a Path, testing only the Path is not enough; check its content or other observable effect. In the lesson's browser task, fix the current str(lines) mistake, preserve UTF-8 text, and handle the empty report. Then use the local pytest example to see the actual fixture behavior on your computer.
Understand the concept
The search pytest tmp_path usually starts with a practical need: a function writes a file, and a test must check it without touching real project data. Add tmp_path to a pytest test function's parameters. pytest supplies a pathlib.Path for a directory unique to that test invocation. Pass this path to your function rather than hard-coding a filename in the current working directory. In a local environment, install pytest with python -m pip install pytest, save a test_*.py file, and run python -m pytest -q.
Build paths with the / operator, for example tmp_path / 'report.txt'. The fixture directory already exists, but a subdirectory such as tmp_path / 'output' does not: create it if the application expects it. Test the result on disk with read_text(encoding='utf-8'), not only the function's return value. A test that asserts the returned Path but never reads the file can pass even when the contents are wrong or nothing was written.
Each test gets its own temporary path, which makes tests independent of one another and of files left by earlier runs. pytest may retain recent temporary directories to aid debugging; do not assume they are erased immediately after each test. tmp_path_factory serves a different purpose: a broader-scope fixture can build reusable temporary data. Start with tmp_path for one-test file behavior. The browser practice panel does not run pytest fixtures, so its equivalent uses tempfile.TemporaryDirectory to rehearse the same file-writing contract.
A useful boundary is to make the function accept a destination Path. Then the same implementation works with a real directory in the application and with tmp_path in tests. In this lesson, save_report(folder, lines) writes UTF-8 lines with a final newline and returns its Path. Tests cover content, an empty report, and a second destination. Keep assertions about paths and content separate so a failure points to the actual problem.
- pytest injects tmp_path into a test function
- tmp_path is a pathlib.Path
- Assert actual disk content
- Pass the destination as a dependency
See it step by step
Read the code, predict the output, then compare it with the result.
01. Write through a supplied directory
from pathlib import Path
import tempfile
with tempfile.TemporaryDirectory() as folder:
path = Path(folder) / 'report.txt'
path.write_text('Ada\nGrace\n', encoding='utf-8')
print(path.read_text(encoding='utf-8').splitlines())['Ada', 'Grace']TemporaryDirectory stands in for pytest's tmp_path in the browser example; both isolate file output.
02. Check the empty-file edge case
from pathlib import Path
import tempfile
with tempfile.TemporaryDirectory() as folder:
path = Path(folder) / 'report.txt'
path.write_text('', encoding='utf-8')
print(path.exists(), path.read_text(encoding='utf-8') == '')True TrueThe test verifies both that the file exists and that no accidental newline was written.
03. Two destinations remain independent
from pathlib import Path
import tempfile
with tempfile.TemporaryDirectory() as left, tempfile.TemporaryDirectory() as right:
(Path(left) / 'report.txt').write_text('one', encoding='utf-8')
(Path(right) / 'report.txt').write_text('two', encoding='utf-8')
print((Path(left) / 'report.txt').read_text(), (Path(right) / 'report.txt').read_text())one twoSeparate directory arguments avoid state leaking between file-writing checks.
Run the real pytest fixture on your computer
Save this as test_report.py, install pytest in the same Python environment, then run python -m pytest -q.
from pathlib import Path
def save_report(folder: Path, lines: list[str]) -> Path:
path = folder / 'report.txt'
path.write_text('\n'.join(lines) + ('\n' if lines else ''), encoding='utf-8')
return path
def test_report_contents(tmp_path: Path) -> None:
path = save_report(tmp_path, ['Ada', 'Grace'])
assert path == tmp_path / 'report.txt'
assert path.read_text(encoding='utf-8') == 'Ada\nGrace\n'
def test_empty_report(tmp_path: Path) -> None:
assert save_report(tmp_path, []).read_text(encoding='utf-8') == ''A closer look
Follow the reasoning, inspect each result, then try the suggested changes in the console below.
Give file-writing code a destination
A test cannot safely assert behavior if the application always writes to a fixed path in the repository. The first design move is small: make the writer accept a folder argument and return the Path it wrote. In a real pytest test, tmp_path supplies that folder as an existing, test-specific directory. The browser console uses TemporaryDirectory to demonstrate the same dependency boundary without pretending to execute pytest.
Build the filename as Path(folder) / 'report.txt'. pathlib handles the platform's separators; string concatenation is unnecessary. Write and read with the same explicit UTF-8 encoding. The first check below demonstrates the wrong but common shortcut: asserting a path exists says nothing about its contents. Inspect the stored text to make the side effect visible.
from pathlib import Path
import tempfile
with tempfile.TemporaryDirectory() as folder:
report = Path(folder) / 'report.txt'
report.write_text('Ada\n', encoding='utf-8')
print(report.exists())
print(repr(report.read_text(encoding='utf-8')))True
'Ada\n'- Accept a folder parameter instead of using a fixed project filename.
- Write UTF-8 text and read it back from the returned Path.
Let pytest inject tmp_path
The local test function declares tmp_path as a parameter. pytest recognizes the fixture name and passes a pathlib.Path pointing to a fresh directory for that test invocation. You do not construct or call tmp_path yourself. The fixture directory already exists, although a child directory you create beneath it still needs mkdir. Place assertions about the returned filename and the file's contents in the same test when both are part of the contract.
Run python -m pytest -q in the environment where pytest is installed. The two tests on the page each receive independent destinations, so the empty-report test cannot pass by accidentally reading the previous test's file. pytest may retain the directories of recent runs for debugging. That retention does not make them shared test input; each invocation still receives its own path.
from pathlib import Path
import tempfile
def save_report(folder, lines):
path = Path(folder) / 'report.txt'
path.write_text('\n'.join(lines) + ('\n' if lines else ''), encoding='utf-8')
return path
with tempfile.TemporaryDirectory() as folder:
path = save_report(folder, ['Ada', 'Grace'])
print(path.name, repr(path.read_text(encoding='utf-8')))report.txt 'Ada\nGrace\n'- Save the localRunCode as test_report.py and run python -m pytest -q.
- Assert both the returned Path and the exact saved text.
Test the edge case separately
An empty list should still create a report file, but with zero characters. A function that always appends a newline produces a different result. Decide which result your application promises, then assert that exact output. This example chooses an empty file. It also verifies a final newline for a nonempty report, which keeps line-oriented tools happy without adding one to the zero-line case.
When a test fails, use the returned Path and read_text to understand the mismatch. Avoid changing the application's output just to match a loose assertion such as 'contains Ada'. A precise contract catches extra brackets, Python list representations, wrong encodings, and missing newlines. If your application writes multiple related files, create all paths under the supplied tmp_path so the test remains isolated.
from pathlib import Path
import tempfile
with tempfile.TemporaryDirectory() as folder:
path = Path(folder) / 'report.txt'
path.write_text('', encoding='utf-8')
print(path.exists(), len(path.read_text(encoding='utf-8')))True 0- Assert the empty-file result directly.
- Use a second case with two lines to confirm separators and final newline.
Try it in Python
Edit the example and run it. Python starts in your browser the first time you click Run.
Python console
Ready to runNeed input()? Add one value per line
Your output appears here.
Need a hint?
Use '\n'.join(lines), append a newline only when lines is nonempty, and call Path.write_text(..., encoding='utf-8').
Complete the task and select Check task to verify your code.
Quick quiz
Three questions. You can change your answers and try again.
Typical mistakes
Everyone meets these errors. See what causes them and how to fix them.
Writing into the repository
Path('report.txt').write_text(data)Pass tmp_path to the writer and build tmp_path / 'report.txt'.What happens: Tests depend on the working directory and can overwrite a real file.
A destination parameter makes the file side effect testable and isolated.
Checking only a return value
assert save_report(tmp_path, lines).name == 'report.txt'Read the returned path with encoding='utf-8' and compare the text.What happens: The test may pass without confirming the saved content.
The disk content is the behavior this test promises.