Code of the Day
AdvancedTesting Automation Scripts

Mocking the filesystem

Use pytest's tmp_path fixture and unittest.mock.patch to test file-writing functions cleanly, without leaving files behind or touching production directories.

WorkflowAdvanced10 min read
Recommended first
By the end of this lesson you will be able to:
  • Use pytest's tmp_path fixture to test a file-writing function against a real but temporary directory
  • Verify file contents after a write using pathlib
  • Use unittest.mock.patch to mock builtins.open when a real path is unavailable

The previous lesson established that you should test your code, not the OS. That means file-writing functions need a real filesystem for their I/O (to confirm the bytes are correct) but not the production filesystem. pytest's tmp_path fixture provides exactly that: a fresh, isolated directory for each test, automatically removed when the test finishes.

Testing a CSV writer with tmp_path

The demo below defines a function that writes a list of records to a CSV file, then shows how to test it. The key insight: pass the output path as a parameter so the test can supply a tmp_path-based location rather than a hardcoded one.

Python — editable, runs in your browser

Notice that the test reads the file back and checks individual lines rather than comparing the whole string — this makes failures more informative.

Patching builtins.open

Sometimes you cannot inject the path as a parameter — the function has it hardcoded or reads it from config. unittest.mock.patch replaces the real open with a mock for the duration of the test:

from unittest.mock import patch, mock_open
import json

def load_config():
    """Reads config from a hardcoded location — not injectable."""
    with open("/etc/myapp/config.json") as f:
        return json.load(f)

def test_load_config():
    fake_config = json.dumps({"api_key": "test-key", "retries": 3})
    with patch("builtins.open", mock_open(read_data=fake_config)):
        result = load_config()
    assert result["api_key"] == "test-key"
    assert result["retries"] == 3

mock_open is a factory that produces a mock file object whose .read() returns read_data. For JSON, that is enough; for CSV-by-line reading you may need to configure mock_open(read_data=...).return_value.__iter__ manually, at which point switching to tmp_path is usually cleaner.

Prefer tmp_path over mock_open whenever you can. tmp_path exercises the actual open/write/read cycle, which catches encoding bugs and newline issues that a mock never would.

Checking that a function deletes the right file

Deletions are just as testable. Create the file in tmp_path, call the function, assert the file is gone:

def test_cleanup_removes_temp_files(tmp_path):
    temp = tmp_path / "run.tmp"
    temp.write_text("leftover")

    cleanup(tmp_path)   # function under test

    assert not temp.exists()

Where to go next

Next: mocking HTTP — why you should not hit real APIs in tests, and the responses library that lets you intercept requests calls at the transport layer.

Finished reading? Mark it complete to track your progress.

On this page