pytest’s tmpdir fixture is your best friend for tests that need to create files, but its real power lies in how it cleverly isolates those files from your actual project.
Let’s see it in action. Imagine you have a Python script that writes some data to a file, and you want to test that script without cluttering your project directory.
# your_module.py
import os
def write_data(filename, data):
with open(filename, "w") as f:
f.write(data)
def read_data(filename):
if not os.path.exists(filename):
return None
with open(filename, "r") as f:
return f.read()
Now, to test write_data and read_data using tmpdir:
# test_your_module.py
import your_module
import pytest
def test_write_and_read(tmpdir):
# tmpdir is a py.path.local object representing a unique directory
# created by pytest for this test function.
filename = tmpdir.join("my_test_file.txt")
data_to_write = "This is some test data."
your_module.write_data(str(filename), data_to_write)
# Verify the file exists within the temporary directory
assert filename.exists()
# Read the data back and assert its content
read_content = your_module.read_data(str(filename))
assert read_content == data_to_write
# You can even create subdirectories
subdir = tmpdir.mkdir("subdir")
sub_filename = subdir.join("another_file.txt")
your_module.write_data(str(sub_filename), "Data in subdir")
assert sub_filename.exists()
assert your_module.read_data(str(sub_filename)) == "Data in subdir"
When you run pytest, it automatically creates a unique temporary directory for each test function that requests the tmpdir fixture. This directory is typically located within a .pytest_cache directory in your project root or a system temporary directory, depending on your configuration. After the test finishes, pytest cleans up this directory and all its contents, leaving your project pristine. The py.path.local object tmpdir provides convenient methods like join() and mkdir() for creating paths and directories within this temporary space, and it can be easily converted to a string using str() when you need to pass it to standard Python file operations.
The core problem tmpdir solves is test isolation. Without it, tests that write to files would either:
- Modify actual project files, leading to unpredictable test runs and potential data loss.
- Require manual cleanup after each test, making tests fragile and cumbersome.
- Need complex setup and teardown logic to create and remove specific directories and files.
tmpdir abstracts all this away. It provides a clean slate for every test, ensuring that tests don’t interfere with each other or with your project’s codebase. It’s essentially a sandbox for your test’s file I/O operations.
Internally, pytest manages these temporary directories. When a test function declares tmpdir as an argument, pytest’s fixture mechanism intercepts this. It creates a new, empty directory before the test function executes. This directory is guaranteed to be unique for that specific test run and test function. After the test function completes, regardless of whether it passed or failed, pytest recursively removes the temporary directory and all its contents. This ensures that no artifacts are left behind.
The py.path.local object that tmpdir provides is more than just a string path. It’s a powerful object from the py library (which pytest bundles) that offers object-oriented file system operations. For example, tmpdir.join("my_file.txt") creates a py.path.local object representing your_temp_dir/my_file.txt. You can then call .read(), .write(), .remove(), .mkdir(), .listdir(), and many other methods directly on these objects. This makes file manipulation within tests more readable and less error-prone than string concatenation for paths.
When you pass a py.path.local object to a function that expects a string path (like open()), you must explicitly convert it using str(your_path_object). This is a common point of confusion for newcomers.
A subtle but crucial aspect of tmpdir is its scope. By default, tmpdir is function-scoped. This means a new temporary directory is created for every single test function that uses it. If you have a test class and want a shared temporary directory for all tests within that class, you would use the tmpdir_factory fixture to create a directory once for the class or module. The tmpdir_factory fixture itself is a factory object that you call to create temporary directories on demand, providing more control over their lifecycle and naming.
The next concept you’ll likely encounter is managing larger or more complex file structures within your tests, often leading to the py.path.local object’s ability to create multiple files and directories efficiently, or using pytest.fixture with a custom scope for more advanced lifecycle management.