| Usage | |
| ===== | |
| Test Scenarios | |
| -------------- | |
| There are several approaches for implementing tests using ``pyfakefs``. | |
| Patch using the pytest plugin | |
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
| ``pyfakefs`` functions as a `pytest`_ plugin that provides the `fs` fixture, | |
| which is registered at installation time. | |
| Using this fixture automatically patches all file system functions with | |
| the fake file system functions. It also allows to access several | |
| convenience methods (see :ref:`convenience_methods`). | |
| Here is an example for a simple test: | |
| .. code:: python | |
| import os | |
| def test_fakefs(fs): | |
| # "fs" is the reference to the fake file system | |
| fs.create_file("/var/data/xx1.txt") | |
| assert os.path.exists("/var/data/xx1.txt") | |
| If you are bothered by the ``pylint`` warning, | |
| ``C0103: Argument name "fs" doesn't conform to snake_case naming style (invalid-name)``, | |
| you can define a longer name in your ``conftest.py`` and use that in your tests: | |
| .. code:: python | |
| import pytest | |
| @pytest.fixture | |
| def fake_filesystem(fs): # pylint:disable=invalid-name | |
| """Variable name 'fs' causes a pylint warning. Provide a longer name | |
| acceptable to pylint for use in tests. | |
| """ | |
| yield fs | |
| Class-, module- and session-scoped fixtures | |
| ........................................... | |
| For convenience, class-, module- and session-scoped fixtures with the same | |
| functionality are provided, named ``fs_class``, ``fs_module`` and ``fs_session``, | |
| respectively. | |
| .. caution:: If any of these fixtures is active, any other ``fs`` fixture will | |
| not setup / tear down the fake filesystem in the current scope; instead, it | |
| will just serve as a reference to the active fake filesystem. That means that changes | |
| done in the fake filesystem inside a test will remain there until the respective scope | |
| ends (see also :ref:`nested_patcher_invocation`). | |
| Patch using fake_filesystem_unittest | |
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
| If you are using the Python ``unittest`` package, the easiest approach is to | |
| use test classes derived from ``fake_filesystem_unittest.TestCase``. | |
| If you call ``setUpPyfakefs()`` in your ``setUp()``, ``pyfakefs`` will | |
| automatically find all real file functions and modules, and stub these out | |
| with the fake file system functions and modules: | |
| .. code:: python | |
| import os | |
| from pyfakefs.fake_filesystem_unittest import TestCase | |
| class ExampleTestCase(TestCase): | |
| def setUp(self): | |
| self.setUpPyfakefs() | |
| def test_create_file(self): | |
| file_path = "/test/file.txt" | |
| self.assertFalse(os.path.exists(file_path)) | |
| self.fs.create_file(file_path) | |
| self.assertTrue(os.path.exists(file_path)) | |
| The usage is explained in more detail in :ref:`auto_patch` and | |
| demonstrated in the files `example.py`_ and `example_test.py`_. | |
| If your setup is the same for all tests in a class, you can use the class setup | |
| method ``setUpClassPyfakefs`` instead: | |
| .. code:: python | |
| import os | |
| import pathlib | |
| from pyfakefs.fake_filesystem_unittest import TestCase | |
| class ExampleTestCase(TestCase): | |
| @classmethod | |
| def setUpClass(cls): | |
| cls.setUpClassPyfakefs() | |
| # setup the fake filesystem using standard functions | |
| path = pathlib.Path("/test") | |
| path.mkdir() | |
| (path / "file1.txt").touch() | |
| # you can also access the fake fs via fake_fs() if needed | |
| cls.fake_fs().create_file("/test/file2.txt", contents="test") | |
| def test1(self): | |
| self.assertTrue(os.path.exists("/test/file1.txt")) | |
| self.assertTrue(os.path.exists("/test/file2.txt")) | |
| def test2(self): | |
| self.assertTrue(os.path.exists("/test/file1.txt")) | |
| file_path = "/test/file3.txt" | |
| # self.fs is the same instance as cls.fake_fs() above | |
| self.fs.create_file(file_path) | |
| self.assertTrue(os.path.exists(file_path)) | |
| .. note:: This feature cannot be used with a Python version before Python 3.8 due to | |
| a missing feature in ``unittest``. If you use ``pytest`` for running tests using this feature, | |
| you need to have at least ``pytest`` version 6.2 due to an issue in earlier versions. | |
| .. caution:: If this is used, any changes made in the fake filesystem inside a test | |
| will remain there for all following tests in the test class, if they are not reverted | |
| in the test itself. | |
| Patch using fake_filesystem_unittest.Patcher | |
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
| If you are using other means of testing like `nose`_, | |
| you can do the patching using ``fake_filesystem_unittest.Patcher``--the class | |
| doing the actual work of replacing the filesystem modules with the fake modules | |
| in the first two approaches. | |
| The easiest way is to just use ``Patcher`` as a context manager: | |
| .. code:: python | |
| from pyfakefs.fake_filesystem_unittest import Patcher | |
| with Patcher() as patcher: | |
| # access the fake_filesystem object via patcher.fs | |
| patcher.fs.create_file("/foo/bar", contents="test") | |
| # the following code works on the fake filesystem | |
| with open("/foo/bar") as f: | |
| contents = f.read() | |
| You can also initialize ``Patcher`` manually: | |
| .. code:: python | |
| from pyfakefs.fake_filesystem_unittest import Patcher | |
| patcher = Patcher() | |
| patcher.setUp() # called in the initialization code | |
| ... | |
| patcher.tearDown() # somewhere in the cleanup code | |
| Patch using fake_filesystem_unittest.patchfs decorator | |
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
| This is basically a convenience wrapper for the previous method. | |
| If you are not using ``pytest`` and want to use the fake filesystem for a | |
| single function, you can write: | |
| .. code:: python | |
| from pyfakefs.fake_filesystem_unittest import patchfs | |
| @patchfs | |
| def test_something(fake_fs): | |
| # access the fake_filesystem object via fake_fs | |
| fake_fs.create_file("/foo/bar", contents="test") | |
| Note that ``fake_fs`` is a positional argument and the argument name does | |
| not matter. If there are additional ``mock.patch`` decorators that also | |
| create positional arguments, the argument order is the same as the decorator | |
| order, as shown here: | |
| .. code:: python | |
| @patchfs | |
| @mock.patch("foo.bar") | |
| def test_something(fake_fs, mocked_bar): | |
| assert foo() | |
| @mock.patch("foo.bar") | |
| @patchfs | |
| def test_something(mocked_bar, fake_fs): | |
| assert foo() | |
| .. note:: | |
| Avoid writing the ``patchfs`` decorator *between* ``mock.patch`` operators, | |
| as the order will not be what you expect. Due to implementation details, | |
| all arguments created by ``mock.patch`` decorators are always expected to | |
| be contiguous, regardless of other decorators positioned between them. | |
| .. caution:: | |
| In previous versions, the keyword argument `fs` has been used instead, | |
| which had to be positioned *after* all positional arguments regardless of | |
| the decorator order. If you upgrade from a version before pyfakefs 4.2, | |
| you may have to adapt the argument order. | |
| You can also use this to make a single unit test use the fake fs: | |
| .. code:: python | |
| class TestSomething(unittest.TestCase): | |
| @patchfs | |
| def test_something(self, fs): | |
| fs.create_file("/foo/bar", contents="test") | |
| .. _customizing_patcher: | |
| Customizing patching | |
| -------------------- | |
| ``fake_filesystem_unittest.Patcher`` provides a few arguments to adapt | |
| patching for cases where it does not work out of the box. These arguments | |
| can also be used with ``unittest`` and ``pytest``. | |
| Using custom arguments | |
| ~~~~~~~~~~~~~~~~~~~~~~ | |
| The following sections describe how to apply these arguments in different | |
| scenarios, using the argument :ref:`allow_root_user` as an example. | |
| Patcher | |
| ....... | |
| If you use the ``Patcher`` directly, you can just pass the arguments in the | |
| constructor: | |
| .. code:: python | |
| from pyfakefs.fake_filesystem_unittest import Patcher | |
| with Patcher(allow_root_user=False) as patcher: | |
| ... | |
| Pytest | |
| ...... | |
| In case of ``pytest``, you have two possibilities: | |
| - The standard way to customize the ``fs`` fixture is to write your own | |
| fixture which uses the ``Patcher`` with arguments as has been shown above: | |
| .. code:: python | |
| import pytest | |
| from pyfakefs.fake_filesystem_unittest import Patcher | |
| @pytest.fixture | |
| def fs_no_root(): | |
| with Patcher(allow_root_user=False) as patcher: | |
| yield patcher.fs | |
| def test_something(fs_no_root): | |
| assert foo() | |
| - You can also pass the arguments using ``@pytest.mark.parametrize``. Note that | |
| you have to provide `all Patcher arguments`_ before the needed ones, as | |
| keyword arguments cannot be used, and you have to add ``indirect=True``. | |
| This makes it less readable, but gives you a quick possibility to adapt a | |
| single test: | |
| .. code:: python | |
| import pytest | |
| @pytest.mark.parametrize("fs", [[None, None, None, False]], indirect=True) | |
| def test_something(fs): | |
| assert foo() | |
| Unittest | |
| ........ | |
| If you are using ``fake_filesystem_unittest.TestCase``, the arguments can be | |
| passed to ``setUpPyfakefs()``, which will pass them to the ``Patcher`` | |
| instance: | |
| .. code:: python | |
| from pyfakefs.fake_filesystem_unittest import TestCase | |
| class SomeTest(TestCase): | |
| def setUp(self): | |
| self.setUpPyfakefs(allow_root_user=False) | |
| def testSomething(self): | |
| assert foo() | |
| patchfs | |
| ....... | |
| If you use the ``patchfs`` decorator, you can pass the arguments directly to | |
| the decorator: | |
| .. code:: python | |
| from pyfakefs.fake_filesystem_unittest import patchfs | |
| @patchfs(allow_root_user=False) | |
| def test_something(fake_fs): | |
| assert foo() | |
| List of custom arguments | |
| ~~~~~~~~~~~~~~~~~~~~~~~~ | |
| Following is a description of the optional arguments that can be used to | |
| customize ``pyfakefs``. | |
| .. _modules_to_reload: | |
| modules_to_reload | |
| ................. | |
| ``Pyfakefs`` patches modules that are imported before starting the test by | |
| finding and replacing file system modules in all loaded modules at test | |
| initialization time. | |
| This allows to automatically patch file system related modules that are: | |
| - imported directly, for example: | |
| .. code:: python | |
| import os | |
| import pathlib.Path | |
| - imported as another name: | |
| .. code:: python | |
| import os as my_os | |
| - imported using one of these two specially handled statements: | |
| .. code:: python | |
| from os import path | |
| from pathlib import Path | |
| Additionally, functions from file system related modules are patched | |
| automatically if imported like: | |
| .. code:: python | |
| from os.path import exists | |
| from os import stat | |
| This also works if importing the functions as another name: | |
| .. code:: python | |
| from os.path import exists as my_exists | |
| from io import open as io_open | |
| from builtins import open as bltn_open | |
| There are a few cases where automatic patching does not work. We know of at | |
| least two specific cases where this is the case: | |
| Initializing a default argument with a file system function is not patched | |
| automatically due to performance reasons (though it can be switched on using | |
| :ref:`patch_default_args`): | |
| .. code:: python | |
| import os | |
| def check_if_exists(filepath, file_exists=os.path.exists): | |
| return file_exists(filepath) | |
| If initializing a global variable using a file system function, the | |
| initialization will be done using the real file system: | |
| .. code:: python | |
| from pathlib import Path | |
| path = Path("/example_home") | |
| In this case, ``path`` will hold the real file system path inside the test. | |
| The same is true, if a file system function is used in a decorator (this is | |
| an example from a related issue): | |
| .. code:: python | |
| import pathlib | |
| import click | |
| @click.command() | |
| @click.argument("foo", type=click.Path(path_type=pathlib.Path)) | |
| def hello(foo): | |
| pass | |
| To get these cases to work as expected under test, the respective modules | |
| containing the code shall be added to the ``modules_to_reload`` argument (a | |
| module list). | |
| The passed modules will be reloaded, thus allowing ``pyfakefs`` to patch them | |
| dynamically. All modules loaded after the initial patching described above | |
| will be patched using this second mechanism. | |
| Given that the example function ``check_if_exists`` shown above is located in | |
| the file ``example/sut.py``, the following code will work (imports are omitted): | |
| .. code:: python | |
| import example | |
| # example using unittest | |
| class ReloadModuleTest(fake_filesystem_unittest.TestCase): | |
| def setUp(self): | |
| self.setUpPyfakefs(modules_to_reload=[example.sut]) | |
| def test_path_exists(self): | |
| file_path = "/foo/bar" | |
| self.fs.create_dir(file_path) | |
| self.assertTrue(example.sut.check_if_exists(file_path)) | |
| # example using pytest | |
| @pytest.mark.parametrize("fs", [[None, [example.sut]]], indirect=True) | |
| def test_path_exists(fs): | |
| file_path = "/foo/bar" | |
| fs.create_dir(file_path) | |
| assert example.sut.check_if_exists(file_path) | |
| # example using Patcher | |
| def test_path_exists(): | |
| with Patcher(modules_to_reload=[example.sut]) as patcher: | |
| file_path = "/foo/bar" | |
| patcher.fs.create_dir(file_path) | |
| assert example.sut.check_if_exists(file_path) | |
| # example using patchfs decorator | |
| @patchfs(modules_to_reload=[example.sut]) | |
| def test_path_exists(fs): | |
| file_path = "/foo/bar" | |
| fs.create_dir(file_path) | |
| assert example.sut.check_if_exists(file_path) | |
| .. note:: If the reloaded modules depend on each other (e.g. one imports the other), | |
| the order in which they are reloaded matters. The dependent module should be reloaded | |
| first, so that on reloading the depending module it is already correctly patched. | |
| modules_to_patch | |
| ................ | |
| Sometimes there are file system modules in other packages that are not | |
| patched in standard ``pyfakefs``. To allow patching such modules, | |
| ``modules_to_patch`` can be used by adding a fake module implementation for | |
| a module name. The argument is a dictionary of fake modules mapped to the | |
| names to be faked. | |
| This mechanism is used in ``pyfakefs`` itself to patch the external modules | |
| `pathlib2` and `scandir` if present, and the following example shows how to | |
| fake a module in Django that uses OS file system functions (note that this | |
| has now been been integrated into ``pyfakefs``): | |
| .. code:: python | |
| import django | |
| class FakeLocks: | |
| """django.core.files.locks uses low level OS functions, fake it.""" | |
| _locks_module = django.core.files.locks | |
| def __init__(self, fs): | |
| """Each fake module expects the fake file system as an __init__ | |
| parameter.""" | |
| # fs represents the fake filesystem; for a real example, it can be | |
| # saved here and used in the implementation | |
| pass | |
| @staticmethod | |
| def lock(f, flags): | |
| return True | |
| @staticmethod | |
| def unlock(f): | |
| return True | |
| def __getattr__(self, name): | |
| return getattr(self._locks_module, name) | |
| ... | |
| # test code using Patcher | |
| with Patcher(modules_to_patch={"django.core.files.locks": FakeLocks}): | |
| test_django_stuff() | |
| # test code using unittest | |
| class TestUsingDjango(fake_filesystem_unittest.TestCase): | |
| def setUp(self): | |
| self.setUpPyfakefs(modules_to_patch={"django.core.files.locks": FakeLocks}) | |
| def test_django_stuff(self): | |
| assert foo() | |
| # test code using pytest | |
| @pytest.mark.parametrize( | |
| "fs", [[None, None, {"django.core.files.locks": FakeLocks}]], indirect=True | |
| ) | |
| def test_django_stuff(fs): | |
| assert foo() | |
| # test code using patchfs decorator | |
| @patchfs(modules_to_patch={"django.core.files.locks": FakeLocks}) | |
| def test_django_stuff(fake_fs): | |
| assert foo() | |
| additional_skip_names | |
| ..................... | |
| This may be used to add modules that shall not be patched. This is mostly | |
| used to avoid patching the Python file system modules themselves, but may be | |
| helpful in some special situations, for example if a testrunner needs to access | |
| the file system after test setup. To make this possible, the affected module | |
| can be added to ``additional_skip_names``: | |
| .. code:: python | |
| with Patcher(additional_skip_names=["pydevd"]) as patcher: | |
| patcher.fs.create_file("foo") | |
| Alternatively to the module names, the modules themselves may be used: | |
| .. code:: python | |
| import pydevd | |
| from pyfakefs.fake_filesystem_unittest import Patcher | |
| with Patcher(additional_skip_names=[pydevd]) as patcher: | |
| patcher.fs.create_file("foo") | |
| .. _allow_root_user: | |
| allow_root_user | |
| ............... | |
| This is ``True`` by default, meaning that the user is considered a root user | |
| if the real user is a root user (e.g. has the user ID 0). If you want to run | |
| your tests as a non-root user regardless of the actual user rights, you may | |
| want to set this to ``False``. | |
| use_known_patches | |
| ................. | |
| Some libraries are known to require patching in order to work with | |
| ``pyfakefs``. | |
| If ``use_known_patches`` is set to ``True`` (the default), ``pyfakefs`` patches | |
| these libraries so that they will work with the fake filesystem. Currently, this | |
| includes patches for ``pandas`` read methods like ``read_csv`` and | |
| ``read_excel``, and for ``Django`` file locks--more may follow. Ordinarily, | |
| the default value of ``use_known_patches`` should be used, but it is present | |
| to allow users to disable this patching in case it causes any problems. | |
| patch_open_code | |
| ............... | |
| Since Python 3.8, the ``io`` module has the function ``open_code``, which | |
| opens a file read-only and is used to open Python code files. By default, this | |
| function is not patched, because the files it opens usually belong to the | |
| executed library code and are not present in the fake file system. | |
| Under some circumstances, this may not be the case, and the opened file | |
| lives in the fake filesystem. For these cases, you can set ``patch_open_code`` | |
| to ``PatchMode.ON``. If you just want to patch ``open_case`` for files that | |
| live in the fake filesystem, and use the real function for the rest, you can | |
| set ``patch_open_code`` to ``PatchMode.AUTO``: | |
| .. code:: python | |
| from pyfakefs.fake_filesystem_unittest import PatchMode | |
| @patchfs(patch_open_code=PatchMode.AUTO) | |
| def test_something(fs): | |
| assert foo() | |
| In this mode, it is also possible to import modules created in the fake filesystem | |
| using `importlib.import_module`. Make sure that the `sys.path` contains the parent path in this case: | |
| .. code:: python | |
| @patchfs(patch_open_code=PatchMode.AUTO) | |
| def test_fake_import(fs): | |
| fake_module_path = Path("/") / "site-packages" / "fake_module.py" | |
| self.fs.create_file(fake_module_path, contents="x = 5") | |
| sys.path.insert(0, str(fake_module_path.parent)) | |
| module = importlib.import_module("fake_module") | |
| assert module.x == 5 | |
| .. _patch_default_args: | |
| patch_default_args | |
| .................. | |
| As already mentioned, a default argument that is initialized with a file | |
| system function is not patched automatically: | |
| .. code:: python | |
| import os | |
| def check_if_exists(filepath, file_exists=os.path.exists): | |
| return file_exists(filepath) | |
| As this is rarely needed, and the check to patch this automatically is quite | |
| expansive, it is not done by default. Using ``patch_default_args`` will | |
| search for this kind of default arguments and patch them automatically. | |
| You could also use the :ref:`modules_to_reload` option with the module that | |
| contains the default argument instead, if you want to avoid the overhead. | |
| .. note:: There are some cases where this option does *not* work: | |
| - if default arguments are *computed* using file system functions: | |
| .. code:: python | |
| import os | |
| def some_function(use_bar=os.path.exists("/foo/bar")): | |
| return do_something() if use_bar else do_something_else() | |
| - if the default argument is an instance of ``pathlib.Path``: | |
| .. code:: python | |
| import pathlib | |
| def foobar(dir_arg=pathlib.Path.cwd() / "logs"): | |
| do_something(dir_arg) | |
| In both cases the default arguments behave like global variables that use a file system function | |
| (which they basically are), and can only be handled using :ref:`modules_to_reload`. | |
| use_cache | |
| ......... | |
| If True (the default), patched and non-patched modules are cached between tests | |
| to avoid the performance hit of the file system function lookup (the | |
| patching itself is reverted after each test). This argument allows to turn it off in case it causes any problems: | |
| .. code:: python | |
| @patchfs(use_cache=False) | |
| def test_something(fake_fs): | |
| fake_fs.create_file("foo", contents="test") | |
| ... | |
| If using ``pytest``, the cache is always cleared before the final test shutdown, as there has been a problem | |
| happening on shutdown related to removing the cached modules. | |
| This does not happen for other test methods so far. | |
| If you think you have encountered a similar problem with ``unittest``, you may try to clear the cache | |
| during module shutdown using the class method for clearing the cache: | |
| .. code:: python | |
| from pyfakefs.fake_filesystem_unittest import Patcher | |
| def tearDownModule(): | |
| Patcher.clear_fs_cache() | |
| Please write an issue if you encounter any problem that can be fixed by using this parameter. | |
| If you want to clear the cache just for a specific test instead, you can call | |
| ``clear_cache`` on the ``Patcher`` or the ``fake_filesystem`` instance: | |
| .. code:: python | |
| def test_something(fs): # using pytest fixture | |
| fs.clear_cache() | |
| ... | |
| .. _use_dynamic_patch: | |
| use_dynamic_patch | |
| ~~~~~~~~~~~~~~~~~ | |
| If ``True`` (the default), dynamic patching after setup is used (for example | |
| for modules loaded locally inside of functions). | |
| Can be switched off if it causes unwanted side effects, though that would mean that | |
| dynamically loaded modules are no longer patched, if they use file system functions. | |
| See also :ref:`failing_dyn_patcher` in the troubleshooting guide for more information. | |
| .. _convenience_methods: | |
| 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 :py:meth:`create_file()<pyfakefs.fake_filesystem.FakeFilesystem.create_file>`, | |
| :py:meth:`create_dir()<pyfakefs.fake_filesystem.FakeFilesystem.create_dir>`, | |
| :py:meth:`create_symlink()<pyfakefs.fake_filesystem.FakeFilesystem.create_symlink>` and | |
| :py:meth:`create_link()<pyfakefs.fake_filesystem.FakeFilesystem.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 I\O operations on the file (may be used to fill up the file system | |
| with large files, see also :ref:`set-fs-size`). | |
| .. code:: python | |
| 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. | |
| .. _real_fs_access: | |
| 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 :py:meth:`add_real_file()<pyfakefs.fake_filesystem.FakeFilesystem.add_real_file>`, | |
| :py:meth:`add_real_directory()<pyfakefs.fake_filesystem.FakeFilesystem.add_real_directory>`, | |
| :py:meth:`add_real_symlink()<pyfakefs.fake_filesystem.FakeFilesystem.add_real_symlink>` and | |
| :py:meth:`add_real_paths()<pyfakefs.fake_filesystem.FakeFilesystem.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. | |
| .. code:: python | |
| 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: | |
| .. code:: python | |
| 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. | |
| .. code:: python | |
| 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 != "" | |
| 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 :py:meth:`add_mount_point()<pyfakefs.fake_filesystem.FakeFilesystem.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. | |
| .. _set-fs-size: | |
| 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 | |
| :py:meth:`set_disk_usage()<pyfakefs.fake_filesystem.FakeFilesystem.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. | |
| .. code:: python | |
| 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 :py:meth:`get_disk_usage()<pyfakefs.fake_filesystem.FakeFilesystem.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: | |
| .. code:: python | |
| 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: | |
| .. code:: python | |
| 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`` - if ``True`` a Windows file system (NTFS) is assumed | |
| - ``is_macos`` - if ``True`` and ``is_windows_fs`` is ``False``, the | |
| standard macOS file system (HFS+) is assumed | |
| - if ``is_windows_fs`` and ``is_macos`` are ``False``, a Linux file system | |
| (something like ext3) is assumed | |
| - ``is_case_sensitive`` is set to ``True`` under Linux and to ``False`` | |
| 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 to | |
| ``None`` under Posix--these can also be adapted if needed | |
| The following test works both under Windows and Linux: | |
| .. code:: python | |
| 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``: | |
| .. code:: python | |
| 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() | |
| .. _`example.py`: https://github.com/pytest-dev/pyfakefs/blob/main/pyfakefs/tests/example.py | |
| .. _`example_test.py`: https://github.com/pytest-dev/pyfakefs/blob/main/pyfakefs/tests/example_test.py | |
| .. _`pytest`: https://doc.pytest.org | |
| .. _`nose`: https://docs.nose2.io/en/latest/ | |
| .. _`all Patcher arguments`: https://pytest-pyfakefs.readthedocs.io/en/latest/modules.html#pyfakefs.fake_filesystem_unittest.Patcher |