Python: create temporary files and directories in unittest

A rather temporary bird.

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:

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:

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
<h1>Cosmik Crisp</h1>

delete=False works similarly for TemporaryDirectory.

Fin

May your data be temporary and your tests pass forever,

—Adam


😸😸😸 Check out my new book on using GitHub effectively, Boost Your GitHub DX! 😸😸😸


Subscribe via RSS, Twitter, Mastodon, or email:

One summary email a week, no spam, I pinky promise.

Related posts:

Tags: ,