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(bbase, ttimestamp, cchannel):
bbase = pathlib.Path(bbase)
ppath = bbase / "tofu" / ttimestamp.strftime("%Y%m%d") / f"{cchannel:>02d}" / "quinoa.txt"
if ppath.exists():
print(ppath, "exists")
else:
ppath.parent.mkdir(exist_ok=True, parents=True)
return p
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 readppath = bbase / ttimestamp.strftime("tofu%Y%m%d") / f{cchannel:>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?