Python: create temporary files and directories in unittest - Adam Johnson (original) (raw)
2024-12-30
Sometimes, tests need temporary files or directories. You can do this in Python’s unittest
with the standard library tempfile module. Let’s look at some recipes to do so within individual tests and setUp()
.
Creating a temporary file
To create a temporary file in a single test:
from tempfile import NamedTemporaryFile from unittest import TestCase
class ExampleTests(TestCase): def test_example(self): temp_file = self.enterContext( NamedTemporaryFile(mode="w+", suffix=".html"), )
# Write to the file
temp_file.write("<h1>Cosmic Crisp</h1>")
# Read from the file
temp_file.seek(0)
result = temp_file.read()
self.assertEqual(result, "<h1>Cosmic Crisp</h1>")
Note:
- enterContext() enters the
NamedTemporaryFile
context manager and runs its exit method at the end of the test. UsingenterContext()
avoids indenting the whole test, as would be required usingwith
.
This method is new in Python 3.11. On older versions, you can copy a backport from my previous post. - The test uses NamedTemporaryFile to create a temporary file. By using it as a context manager, its exit method deletes the temporary file. That prevents littering the filesystem with test files.
NamedTemporaryFile
is used instead ofTemporaryFile
, to make the file visible in the filesystem. This allows other functions or processes to reopen it by name, available in thename
attribute. This is typically required when testing with files, where the system under test typically accepts a file name, like:
render_apple(temp_file.name) - The
mode
is set tow+
, which means “reading and writing, text mode”. The default isw+b
, which is the same but in binary mode. Pick text or binary to match what you’re testing. - The
suffix
is set to.html
, which ensures the created file has that suffix. This isn’t mandatory, but it can make debugging easier. For example, if you need to open the file, it will open with its default program.
Creating a temporary directory
This works similarly:
from tempfile import TemporaryDirectory from unittest import TestCase
class ExampleTests(TestCase): def test_example(self): temp_dir = self.enterContext(TemporaryDirectory())
# Write to a file
with open(f"{temp_dir}/apple.html", "w") as temp_file:
temp_file.write("<h1>Cosmic Crisp</h1>")
# Read from the file
with open(f"{temp_dir}/apple.html", "r") as temp_file:
result = temp_file.read()
self.assertEqual(result, "<h1>Cosmic Crisp</h1>")
Note:
NamedTemporaryFile
has been swapped for TemporaryDirectory. It creates a directory and completely removes it in its exit method.temp_dir
is not an object but a string containing the path of the temporary directory.
Per-test usage in setUp()
To have a temporary file available for each test, hoist the self.enterContext()
to setUp()
:
from tempfile import NamedTemporaryFile from unittest import TestCase
class ExampleTests(TestCase): def setUp(self): super().setUp() self.temp_file = self.enterContext( NamedTemporaryFile(mode="w+", suffix=".html") )
def test_example(self):
# Write to the file
self.temp_file.write("<h1>Cosmic Crisp</h1>")
self.temp_file.seek(0)
# Read from the file
result = self.temp_file.read()
self.assertEqual(result, "<h1>Cosmic Crisp</h1>")
Or, for a temporary directory:
from tempfile import TemporaryDirectory from unittest import TestCase
class ExampleTests(TestCase): def setUp(self): super().setUp() self.temp_dir = self.enterContext(TemporaryDirectory())
def test_example(self):
# Write to a file
with open(f"{self.temp_dir}/apple.html", "w") as temp_file:
temp_file.write("<h1>Cosmic Crisp</h1>")
# Read from the file
with open(f"{self.temp_dir}/apple.html", "r") as temp_file:
result = temp_file.read()
self.assertEqual(result, "<h1>Cosmic Crisp</h1>")
Sprinkle in some pathlib
Using pathlib.Path simplifies the creation of test files within a temporary directory:
from pathlib import Path from tempfile import TemporaryDirectory from unittest import TestCase
class ExampleTests(TestCase): def setUp(self): super().setUp()
self.temp_path = Path(self.enterContext(TemporaryDirectory()))
def test_example(self):
# Write to a file
(self.temp_path / "apple.html").write_text("<h1>Cosmic Crisp</h1>")
# Read from the file
result = (self.temp_path / "apple.html").read_text()
self.assertEqual(result, "<h1>Cosmic Crisp</h1>")
A bonus tip: pair with textwrap.dedent() when creating multi-line files. This can keep the text neatly indented within your test method but dedented in the temporary file:
from pathlib import Path from tempfile import TemporaryDirectory from textwrap import dedent from unittest import TestCase
class ExampleTests(TestCase): def setUp(self): super().setUp()
self.temp_path = Path(self.enterContext(TemporaryDirectory()))
def test_example(self):
# Write to a file
(self.temp_path / "apple.html").write_text(
dedent(
"""
<html>
<body>
<h1>Cosmic Crisp</h1>
</body>
</html>
"""
)
)
# Read from the file
result = (self.temp_path / "apple.html").read_text()
self.assertIn("<h1>Cosmic Crisp</h1>", result)
Debug with delete=False
If tests are failing, you may wish to inspect the contents of a temporary file or directory. To disable the cleanup, add delete=False
to the context manager, plus a print()
to display the path. For example, for a file:
def setUp(self):
super().setUp()
self.temp_file = self.enterContext(
NamedTemporaryFile(mode="w+", suffix=".html")
NamedTemporaryFile(mode="w+", suffix=".html", delete=False) )
print("😅", self.temp_file.name)
Then when tests fail, the emoji will highlight the path (one of my print debugging tips) and you can inspect the file:
$ python -m unittest example 😅 /var/folders/20/lzgtdyzs7wj5fc_90w1h3/T/tmpgpynu7f_.html F
FAIL: test_example (example.ExampleTests.test_example)
... FAILED (failures=1)
$ cat /var/folders/20/lzgtdyzs7wj5fc_90w1h3/T/tmpgpynu7f_.html
Cosmik Crisp
delete=False
works similarly for TemporaryDirectory
.
Fin
May your data be temporary and your tests pass forever,
—Adam
Read my book Boost Your Git DX to Git better.
One summary email a week, no spam, I pinky promise.
Related posts: