How to mock os.listdir to pretend to be files and directories in Python?

I have my own repository format, and I'm trying to develop a Python module to handle these repositories. Repo format:

/home/X/ | + alpha/ | + beta/ | + project.conf 

Here X is the project. alpha and beta are folders within this project, and they represent groups in this project. The group is a container in this repo, and what it represents is really irrelevant to this issue. Repo X also has files at its root level; project.conf is an example of such a file.

I have a class called Project that abstracts projects like X The Project class has a load() method that creates a view in memory.

 class Project(object): def load(self): for entry in os.listdir(self.root): path = os.path.join(self.root, entry) if os.path.isdir(path): group = Group(path) self.groups.append(group) group.load() else: # process files ... 

In the unit test, the load() method, making fun of the file system, I:

 import unittest from unittest import mock import Project class TestRepo(unittest.TestCase): def test_load_project(self): project = Project("X") with mock.patch('os.listdir') as mocked_listdir: mocked_listdir.return_value = ['alpha', 'beta', 'project.conf'] project.load() self.assertEqual(len(project.groups), 2) 

This makes mock os.listdir successful. But I can’t trick Python into treating mocked_listdir.return_value as consisting of files and directories.

How do I mock os.listdir or os.path.isdir in the same test that the test will display alpha and beta as directories and project.conf as a file

+7
python unit-testing mocking
source share
3 answers

I managed to achieve the desired behavior by passing the iterable side_effect attribute of the side_effect object.

 import unittest from unittest import mock import Project class TestRepo(unittest.TestCase): def test_load_project(self): project = Project("X") with mock.patch('os.listdir') as mocked_listdir: with mock.patch('os.path.isdir') as mocked_isdir: mocked_listdir.return_value = ['alpha', 'beta', 'project.conf'] mocked_isdir.side_effect = [True, True, False] project.load() self.assertEqual(len(project.groups), 2) 

The key is the string mocked_isdir.side_effect = [True, True, False] . Boolean values ​​in iterable must match the order of entries in the directory and files passed to the mocked_listdir.return_value attribute.

+2
source share

You can use pyfakefs , this is a very convenient library for testing running on a fake file system.

if you use pytest, it has a plugin, all file system functions are already patched, you just need to use its fs fixture:

 import os def test_foo(fs): fs.CreateFile('/home/x/alpha/1') fs.CreateFile('/home/x/beta/2') fs.CreateFile('/home/x/p.conf') assert os.listdir('/home/x/')) == ['alpha', 'beta', 'p.conf'] 

or if you prefer unittest :

 import os import unittest from pyfakefs import fake_filesystem_unittest class TestRepo(fake_filesystem_unittest.TestCase): def setUp(self): self.setUpPyfakefs() def test_foo(self): os.makedirs('/home/x/alpha') os.makedirs('/home/x/beta') with open('/home/x/p.conf', 'w') as f: f.write('foo') self.assertEqual(os.listdir('/home/x/'), ['alpha', 'beta', 'p.conf']) if __name__ == "__main__": unittest.main() 
+7
source share

It will depend, of course, on what os functions you use, but looking like mock.patch.multiple on os is exactly what you need. (Note that you may not need the path patch, many of its functions are lexical and do not care about the actual file.)

0
source share

All Articles