Motivation:
I want to be able to test a scenario, where my function can't open some file for writing (appending actually, but I don't think this matter) and I want to be able to distinguish this situation from "file does not exist" one.
Example:
import os
from stat import S_IREAD, S_IRGRP, S_IROTH
from typing import Optional
from tempfile import TemporaryDirectory
import unittest
DEFAULT_VAL = 100
def myfunc(fname: str) -> Optional[int]:
if not os.path.isfile(fname):
return DEFAULT_VAL
try:
with open(fname, 'at') as f:
return f.write('bar')
except IOError as e:
return None
class TestMyFunc(unittest.TestCase):
def test_read_file_when_ok(self):
with TemporaryDirectory() as tmpdir:
fn = os.path.join(tmpdir, 'foo.txt')
with open(fn, 'wt') as f:
f.write('')
self.assertEqual(myfunc(fn), 3)
def test_default_when_file_does_not_exist(self):
with TemporaryDirectory() as tmpdir:
fn = os.path.join(tmpdir, 'foo.txt')
self.assertEqual(myfunc(fn), DEFAULT_VAL)
def test_None_when_fails(self):
with TemporaryDirectory() as tmpdir:
fn = os.path.join(tmpdir, 'foo.txt')
with open(fn, 'wt') as f:
f.write('')
os.chmod(fn, S_IREAD | S_IRGRP | S_IROTH) # make it read only
self.assertIsNone(myfunc(fn))
if __name__ == '__main__':
unittest.main()
As you can see, I'm testing this by making the file read-only, which works perfectly fine:
$ python3 a.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.001s
OK
..that is until it failed on bitbucket, because in docker, pipelines run
under root
.
And root
has no problem with opening read only files for writing:
$ sudo python3 a.py
F..
======================================================================
FAIL: test_None_when_fails (__main__.TestMyFunc)
----------------------------------------------------------------------
Traceback (most recent call last):
File "a.py", line 36, in test_None_when_fails
self.assertIsNone(myfunc(fn))
AssertionError: 3 is not None
----------------------------------------------------------------------
Ran 3 tests in 0.001s
FAILED (failures=1)
My fallback solution:
I know I can refactor my code (which is unfortunatelly much larger than myfunc
example) to accept some "file opener" where I can inject some mock which would raise appropriate exception, but I would very much prefer solution which does not require refactoring existing code and which could be isolated in the test.
This is my fallback if there's no other simple (and clean, no monkey-patching) solution.
What I'm looking/hoping for:
I sort-of feel/hope there may be some simple solution based on modifying the file or directory properties..
Question:
Is there a simpler way to simulate "can't open file for writing" so it:
- would work even for root
- would be distinguishable from "file not found"
- would work on linux (i.e. I don't need to support any other platform than Linux)
?
(GNU/Linux Debian 10, python3)
CodePudding user response:
You could mock open
itself:
def test_None_when_fails(self):
with TemporaryDirectory() as tmpdir:
fn = os.path.join(tmpdir, 'foo.txt')
with open(fn, 'wt') as f:
f.write('')
m = unittest.mock.mock_open()
m.side_effect = PermissionError()
with unittest.mock.patch('__main__.open', m):
self.assertIsNone(myfunc(fn))