tmsblgh
tmsblgh

Reputation: 527

Python MagicMock to os.listdir not raise error

I would like to set os.listdir to raise OSError in UT but it not raise anything.

My code:

def get_list_of_files(path):
    try:
        list_of_files = sorted([filename for filename in
                                os.listdir(path) if
                                filename.startswith('FILE')])

    except OSError as error:
    raise Exception(error)

    return list_of_files

def setUp(self):
    self.listdir_patcher = patch('os.listdir')
    self.mock_listdir = self.listdir_patcher.start()
    self.mock_listdir_rv = MagicMock()
    self.mock_listdir.return_value = self.mock_listdir_rv

def tearDown(self):
    self.listdir_patcher.stop()
def test(self):
    e = OSError('abc')
    self.mock_listdir_rv.side_effect = e
    with self.assertRaises(OSError):
         get_list_of_files('path')

What is the problem? (I can not use normal Mock to os.listdir)

Upvotes: 0

Views: 351

Answers (1)

Martijn Pieters
Martijn Pieters

Reputation: 1124988

You need to set the side effect for self.mock_listdir, not it's return value:

def test(self):
    e = OSError('abc')
    self.mock_listdir.side_effect = e
    with self.assertRaises(OSError):
         get_list_of_files('path')

After all, you want the call to os.listdir() to raise the exception, not the call to the return value of os.listdir() (you never use os.listdir()()).

Demo (using patch() as a context manager, which has the same effects as using it as a decorator):

>>> from unittest.mock import patch
>>> import os
>>> with patch('os.listdir') as mock_listdir:
...     mock_listdir.side_effect = OSError('abc')
...     os.listdir('path')
...
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
  File "/Users/mjpieters/Development/Library/buildout.python/parts/opt/lib/python3.6/unittest/mock.py", line 930, in __call__
    return _mock_self._mock_call(*args, **kwargs)
  File "/Users/mjpieters/Development/Library/buildout.python/parts/opt/lib/python3.6/unittest/mock.py", line 986, in _mock_call
    raise effect
OSError: abc

Note that setting the side_effect of the self.mock_listdir mock will persist to other tests! You should really use a new patch per test. You can use patch as a decorator on each test, use this instead of using a per-testcase patcher:

@patch('os.listdir')
def test(self, mock_listdir):
    e = OSError('abc')
    mock_listdir.side_effect = e
    with self.assertRaises(OSError):
         get_list_of_files('path')

If you do stick to using a patcher you start in the setUp, you'd have to clear the side effect afterwards (set it to None).

Aside from all that, there is no need to explicitly create the MagicMock instance for a return_value; that's the default return value already. You could instead store that default:

self.mock_listdir = self.listdir_patcher.start()
self.mock_listdir_rv = self.mock_listdir.return_value

Upvotes: 1

Related Questions