I have a function that constructs a path based on a function of inputs and creates the parent directory.
When writing a unit test for this function, I want to mock the I/O part of the function, namely the .exists() and .mkdir() methods of the pathlib.Path objects. However, since both are not called until the path has been constructed, I find myself reconstructing the implementation using a long chain of return_value.__truediv__ accesses, such as below:
import pathlib
import unittest.mock
import datetime
def get(base, timestamp, channel):
base = pathlib.Path(base)
path = base / "tofu" / timestamp.strftime("%Y%m%d") / f"{channel:>02d}" / "quinoa.txt"
if path.exists():
print(path, "exists")
else:
path.parent.mkdir(exist_ok=True, parents=True)
return path
@unittest.mock.patch("pathlib.Path", autospec=True)
def test_get(pP):
pP.return_value.__truediv__.return_value.__truediv__.return_value.\
__truediv__.return_value.__truediv__.return_value.\
exists.return_value = False
get("quorn", datetime.datetime.now(), 42)
pP.return_value.__truediv__.return_value.__truediv__.return_value.\
__truediv__.return_value.__truediv__.return_value.\
parent.mkdir.assert_called_once()
This test passes, but I have several concerns:
- It is testing implementation rather than functionality. If I were to rewrite the second line in
getto readpath = base / timestamp.strftime("tofu%Y%m%d") / f{channel:>02d}/"quinoa.txt", the functionality would be identical, but the test would fail as I wouldn't be mocking the right value anymore. - It is hard to read and easy to miscount the depth of
__truediv__.return_valuechain.
Apart from moving the I/O out of get, can I mock this in another way, that does not share those limitations? Or does the difficulty of writing a good test (with mock) telling me my function is too complex, despite it's small size?