Search code examples
pythonpathlibpyfakefs

Why FileNotFoundError on Path.rename while using Pyfakefs?


I wrote a test for a function that renames files from e.g. /videos/vid_youtube.mp4 to /videos/youtube/vid.mp4. The test patches the fs with Pyfakefs.

When the code actually renames the file, I get this error.

FileNotFoundError: [Errno 2] No such file or directory: '/home/user/code/project/test/DLV/videos/vid_youtube.mp4' -> '/home/user/code/project/test/DLV/videos/youtube/vid.mp4'

This is how I set up fakefs

def setUp(self) -> None:
    self.setUpPyfakefs()
    self.fs.create_dir(Path(Dirs.VIDEOS))  # /home/user/code/project/test/DLV/videos
    self.fs.create_file(Path(Dirs.VIDEOS / "vid_youtube.mp4"))

The code under test.

class Files:
    @staticmethod
    def rename_video_files():
        all_files = Collect.video_files()

        for files_for_type in all_files:
            for file in all_files[files_for_type]:
                path = Path(file)
                platform = Files.detect_platform(path)
                platform_dir = Path(Dirs.VIDEOS, platform)
                platform_dir.mkdir(exist_ok=True)

                new_name = path.stem.replace(f'_{platform}', '')
                new_path = Dirs.VIDEOS / platform / f'{new_name}{path.suffix}'

                old_path = Dirs.VIDEOS / path
                old_path.rename(new_path)   # throws FileNotFoundError

I debugged the test and the method under test and even passed the fake fs to rename_video_files(fakefs) to inspect the files and directories. All files and directories look correct.

What is going wrong here?


Solution

  • The problem here is most likely the static initialization of Dirs.VIDEOS. This is initialized at load time as a pathlib.Path, and won't be patched later at the time you setup pyfakefs (the same problem would happen if you where to use unittest.patch for patching).

    There are two ways to fix this:

    • adapt the code to not initialize the path statically
      This could be done by statically defining the str path, and converting it to a Path at run time, or by using a method to get the path instead of an attribute (e.g. Dirs.VIDEO() instead of Dirs.VIDEO`).
    • adapt the test to reload the tested code
      If reloading the tested code after pyfakefs has been initialized, it will be correctly patched. pyfakefs provides an argument in setUpPyfakefs that does that:
    from pyfakefs.fake_filesystem_unittest import TestCase
    
    from my_module import video_files
    from my_module.video_files import Dirs, Files
    
    
    class MyTest(TestCase):
        def setUp(self) -> None:
            self.setUpPyfakefs(modules_to_reload=[video_files])
            self.fs.create_dir(
                Path(Dirs.VIDEOS))  # /home/user/code/project/test/DLV/videos
            self.fs.create_file(Path(Dirs.VIDEOS / "vid_youtube.mp4"))
    

    (under the assumption, that your code under test is located in my_module.video_files.py)

    Disclaimer:
    I'm a contributor to pyfakefs.