How do I mock the filesystem in Python unit tests? How do I mock the filesystem in Python unit tests? python python

How do I mock the filesystem in Python unit tests?


pyfakefs (homepage) does what you want – a fake filesystem; it’s third-party, though that party is Google. See How to replace file-access references for a module under test for discussion of use.

For mocking, unittest.mock is the standard library for Python 3.3+ (PEP 0417); for earlier version see PyPI: mock (for Python 2.5+) (homepage).

Terminology in testing and mocking is inconsistent; using the Test Double terminology of Gerard Meszaros, you’re asking for a “fake”: something that behaves like a filesystem (you can create, open, and delete files), but isn’t the actual file system (in this case it’s in-memory), so you don’t need to have test files or a temporary directory.

In classic mocking, you would instead mock out the system calls (in Python, mock out functions in the os module, like os.rm and os.listdir), but that’s much more fiddly.


pytest is gaining a lot of traction, and it can do all of this using tmpdir and monkeypatching (mocking).

You can use the tmpdir function argument which will provide a temporary directory unique to the test invocation, created in the base temporary directory (which are by default created as sub-directories of the system temporary directory).

import osdef test_create_file(tmpdir):    p = tmpdir.mkdir("sub").join("hello.txt")    p.write("content")    assert p.read() == "content"    assert len(tmpdir.listdir()) == 1

The monkeypatch function argument helps you to safely set/delete an attribute, dictionary item or environment variable or to modify sys.path for importing.

import osdef test_some_interaction(monkeypatch):    monkeypatch.setattr(os, "getcwd", lambda: "/")

You can also pass it a function instead of using lambda.

import os.pathdef getssh(): # pseudo application code    return os.path.join(os.path.expanduser("~admin"), '.ssh')def test_mytest(monkeypatch):    def mockreturn(path):        return '/abc'    monkeypatch.setattr(os.path, 'expanduser', mockreturn)    x = getssh()    assert x == '/abc/.ssh'# You can still use lambda when passing arguments, e.g.# monkeypatch.setattr(os.path, 'expanduser', lambda x: '/abc')

If your application has a lot of interaction with the file system, then it might be easier to use something like pyfakefs, as mocking would become tedious and repetitive.


The standard mocking framework in Python 3.3+ is unittest.mock; you can use this for the filesystem or anything else.

You could also simply hand roll it by mocking via monkey patching:

A trivial example:

import os.pathos.path.isfile = lambda path: path == '/path/to/testfile'

A bit more full (untested):

import classtobetested                                                                                                                                                                                      import unittest                                                                                                                                                                                             import contextlib                                                                                                                                                                                           @contextlib.contextmanager                                                                                                                                                                                  def monkey_patch(module, fn_name, patch):                                                                                                                                                                       unpatch = getattr(module, fn_name)                                                                                                                                                                          setattr(module, fn_name)                                                                                                                                                                                    try:                                                                                                                                                                                                            yield                                                                                                                                                                                                   finally:                                                                                                                                                                                                        setattr(module, fn_name, unpatch)                                                                                                                                                                   class TestTheClassToBeTested(unittest.TestCase):                                                                                                                                                                  def test_with_fs_mocks(self):                                                                                                                                                                                   with monkey_patch(classtobetested.os.path,                                                                                                                                                                                    'isfile',                                                                                                                                                                                                   lambda path: path == '/path/to/file'):                                                                                                                                                        self.assertTrue(classtobetested.testable())                 

In this example, the actual mocks are trivial, but you could back them with something that has state so that can represent filesystem actions, such as save and delete. Yes, this is all a bit ugly since it entails replicating/simulating basic filesystem in code.

Note that you can't monkey patch python builtins. That being said...

For earlier versions, if at all possible use a third party library, I'd go with Michael Foord's awesome Mock, which is now unittest.mock in the standard library since 3.3+ thanks to PEP 0417, and you can get it on PyPI for Python 2.5+. And, it can mock builtins!