Using convenience methods¶
While pyfakefs
can be used just with the standard Python file system
functions, there are a few convenience methods in fake_filesystem
that can
help you setting up your tests. The methods can be accessed via the
fake_filesystem
instance in your tests: Patcher.fs
, the fs
fixture in pytest, TestCase.fs
for unittest
, and the positional argument
for the patchfs
decorator.
File creation helpers¶
To create files, directories or symlinks together with all the directories
in the path, you may use create_file()
,
create_dir()
,
create_symlink()
and
create_link()
, respectively.
create_file()
also allows you to set the file mode and the file contents
together with the encoding if needed. Alternatively, you can define a file
size without contents–in this case, you will not be able to perform
standard IO operations on the file (may be used to fill up the file system
with large files, see also Setting the file system size).
from pyfakefs.fake_filesystem_unittest import TestCase
class ExampleTestCase(TestCase):
def setUp(self):
self.setUpPyfakefs()
def test_create_file(self):
file_path = "/foo/bar/test.txt"
self.fs.create_file(file_path, contents="test")
with open(file_path) as f:
self.assertEqual("test", f.read())
create_dir()
behaves like os.makedirs()
.
create_symlink
and create_link
behave like os.symlink
and
os.link
, with any missing parent directories of the link created
automatically.
Caution
The first two arguments in create_symlink
are reverted in relation to
os.symlink
for historical reasons.
Access to files in the real file system¶
If you want to have read access to real files or directories, you can map
them into the fake file system using add_real_file()
,
add_real_directory()
,
add_real_symlink()
and
add_real_paths()
.
They take a file path, a directory path, a symlink path, or a list of paths,
respectively, and make them accessible from the fake file system. By
default, the contents of the mapped files and directories are read only on
demand, so that mapping them is relatively cheap. The access to the files is
by default read-only, but even if you add them using read_only=False
,
the files are written only in the fake system (e.g. in memory). The real
files are never changed.
add_real_file()
, add_real_directory()
and add_real_symlink()
also
allow you to map a file or a directory tree into another location in the
fake filesystem via the argument target_path
. If the target directory already exists
in the fake filesystem, the directory contents are merged. If a file in the fake filesystem
would be overwritten by a file from the real filesystem, an exception is raised.
import os
from pyfakefs.fake_filesystem_unittest import TestCase
class ExampleTestCase(TestCase):
fixture_path = os.path.join(os.path.dirname(__file__), "fixtures")
def setUp(self):
self.setUpPyfakefs()
# make the file accessible in the fake file system
self.fs.add_real_directory(self.fixture_path)
def test_using_fixture(self):
with open(os.path.join(self.fixture_path, "fixture1.txt")) as f:
# file contents are copied to the fake file system
# only at this point
contents = f.read()
You can do the same using pytest
by using a fixture for test setup:
import pytest
import os
fixture_path = os.path.join(os.path.dirname(__file__), "fixtures")
@pytest.fixture
def my_fs(fs):
fs.add_real_directory(fixture_path)
yield fs
@pytest.mark.usefixtures("my_fs")
def test_using_fixture():
with open(os.path.join(fixture_path, "fixture1.txt")) as f:
contents = f.read()
Note
If you are not using the fixture directly in the test, you can use @pytest.mark.usefixtures instead of passing the fixture as an argument. This avoids warnings about unused arguments from linters.
When using pytest
another option is to load the contents of the real file
in a fixture and pass this fixture to the test function before passing
the fs
fixture.
import pytest
import os
@pytest.fixture
def content():
fixture_path = os.path.join(os.path.dirname(__file__), "fixtures")
with open(os.path.join(fixture_path, "fixture1.txt")) as f:
contents = f.read()
return contents
def test_using_file_contents(content, fs):
fs.create_file("fake/path.txt")
assert content != ""
Map package metadata files into fake filesystem¶
A more specialized function for adding real files to the fake filesystem is
add_package_metadata()
.
It adds the metadata distribution files for a given package to the fake filesystem,
so that it can be accessed by modules like importlib.metadata. This is needed
for example if using flask.testing with pyfakefs
.
import pytest
@pytest.fixture(autouse=True)
def add_werkzeug_metadata(fs):
# flask.testing accesses Werkzeug metadata, map it
fs.add_package_metadata("Werkzeug")
yield
Handling mount points¶
Under Linux and macOS, the root path (/
) is the only mount point created
in the fake file system. If you need support for more mount points, you can add
them using add_mount_point()
.
Under Windows, drives and UNC paths are internally handled as mount points. Adding a file or directory on another drive or UNC path automatically adds a mount point for that drive or UNC path root if needed. Explicitly adding mount points shall not be needed under Windows.
A mount point has a separate device ID (st_dev
) under all systems, and
some operations (like rename
) are not possible for files located on
different mount points. The fake file system size (if used) is also set per
mount point.
Setting the file system size¶
If you need to know the file system size in your tests (for example for
testing cleanup scripts), you can set the fake file system size using
set_disk_usage()
. By default, this sets the total size in bytes of the
root partition; if you add a path as parameter, the size will be related to
the mount point (see above) the path is related to.
By default, the size of the fake file system is set to 1 TB (which for most tests can be considered as infinite). As soon as you set a size, all files will occupy the space according to their size, and you may fail to create new files if the fake file system is full.
import errno
import os
from pyfakefs.fake_filesystem_unittest import TestCase
class ExampleTestCase(TestCase):
def setUp(self):
self.setUpPyfakefs()
self.fs.set_disk_usage(100)
def test_disk_full(self):
os.mkdir("/foo")
with self.assertRaises(OSError) as e:
with open("/foo/bar.txt", "w") as f:
f.write("a" * 200)
self.assertEqual(errno.ENOSPC, e.exception.errno)
To get the file system size, you may use get_disk_usage()
, which is
modeled after shutil.disk_usage()
.
Suspending patching¶
Sometimes, you may want to access the real filesystem inside the test with
no patching applied. This can be achieved by using the pause/resume
functions, which exist in fake_filesystem_unittest.Patcher
,
fake_filesystem_unittest.TestCase
and fake_filesystem.FakeFilesystem
.
There is also a context manager class fake_filesystem_unittest.Pause
which encapsulates the calls to pause()
and resume()
.
Here is an example that tests the usage with the pyfakefs
pytest fixture:
import os
import tempfile
from pyfakefs.fake_filesystem_unittest import Pause
def test_pause_resume_contextmanager(fs):
fake_temp_file = tempfile.NamedTemporaryFile()
assert os.path.exists(fake_temp_file.name)
fs.pause()
assert not os.path.exists(fake_temp_file.name)
real_temp_file = tempfile.NamedTemporaryFile()
assert os.path.exists(real_temp_file.name)
fs.resume()
assert not os.path.exists(real_temp_file.name)
assert os.path.exists(fake_temp_file.name)
Here is the same code using a context manager:
import os
import tempfile
from pyfakefs.fake_filesystem_unittest import Pause
def test_pause_resume_contextmanager(fs):
fake_temp_file = tempfile.NamedTemporaryFile()
assert os.path.exists(fake_temp_file.name)
with Pause(fs):
assert not os.path.exists(fake_temp_file.name)
real_temp_file = tempfile.NamedTemporaryFile()
assert os.path.exists(real_temp_file.name)
assert not os.path.exists(real_temp_file.name)
assert os.path.exists(fake_temp_file.name)
Simulating other file systems¶
Pyfakefs
supports Linux, macOS and Windows operating systems. By default,
the file system of the OS where the tests run is assumed, but it is possible
to simulate other file systems to some extent. To set a specific file
system, you can change pyfakefs.FakeFilesystem.os
to one of
OSType.LINUX
, OSType.MACOS
and OSType.WINDOWS
. On doing so, the
behavior of pyfakefs
is adapted to the respective file system. Note that
setting this causes the fake file system to be reset, so you should call it
before adding any files.
Setting the os
attributes changes a number of pyfakefs.FakeFilesystem
attributes, which can also be set separately if needed:
is_windows_fs
- ifTrue
a Windows file system (NTFS) is assumed
is_macos
- ifTrue
andis_windows_fs
isFalse
, the standard macOS file system (HFS+) is assumedif
is_windows_fs
andis_macos
areFalse
, a Linux file system (something like ext3) is assumed
is_case_sensitive
is set toTrue
under Linux and toFalse
under Windows and macOS by default - you can change it to change the respective behavior
path_separator
is set to\
under Windows and to/
under Posix,alternative_path_separator
is set to/
under Windows and toNone
under Posix–these can also be adapted if needed
The following test works both under Windows and Linux:
import os
from pyfakefs.fake_filesystem import OSType
def test_windows_paths(fs):
fs.os = OSType.WINDOWS
assert r"C:\foo\bar" == os.path.join("C:\\", "foo", "bar")
assert os.path.splitdrive(r"C:\foo\bar") == ("C:", r"\foo\bar")
assert os.path.ismount("C:")
Note
Only behavior not relying on OS-specific functionality is emulated on another system.
For example, if you use the Linux-specific functionality of extended attributes (os.getxattr
etc.)
in your code, you have to test this under Linux.
Set file as inaccessible under Windows¶
Normally, if you try to set a file or directory as inaccessible using chmod
under
Windows, the value you provide is masked by a value that always ensures that no read
permissions for any user are removed. In reality, there is the possibility to make
a file or directory unreadable using the Windows ACL API, which is not directly
supported in the Python filesystem API. To make this possible to test, there is the
possibility to use the force_unix_mode
argument to FakeFilesystem.chmod
:
import pathlib
import pytest
from pyfakefs.fake_filesystem import OSType
def test_is_file_for_unreadable_dir_windows(fs):
fs.os = OSType.WINDOWS
path = pathlib.Path("/foo/bar")
fs.create_file(path)
# normal chmod does not really set the mode to 0
fs.chmod("/foo", 0o000)
assert path.is_file()
# but it does in forced UNIX mode
fs.chmod("/foo", 0o000, force_unix_mode=True)
with pytest.raises(PermissionError):
path.is_file()