I have a program that needs to write different files with different content depending on some logic.
I tried to write a assert_file_written_with_content(filepath: str, content: str) function that would access a mock_open and check that content had been attempted to be written to that filepath.
This is my attempt
@pytest.fixture(autouse=True)
def mock_open_file(self, mocker):
mocker.patch("builtins.open", mock_open(), create=True)
def assert_file_written_with_content(file_path, content):
"""When file is opened using builtins.open and this is mocked in the test, assert that the file is written with the given content."""
mock_open_file = builtins.open # This will be mock_open
for call in mock_open_file.call_args_list:
if call.args[0] == file_path and call.args[1] == "w":
call.return_value.write.assert_called_with(content)
return
raise AssertionError(f"File {file_path} was not written with content {content}")
For some reason, it is always passing; even when the content did not match.
Why is there no assertion error being raised when the content is wrong?
Bonus points if you can have the test print the comparison in filepaths and content if there's a mismatch when run through pytest.
Update:
I now have this, which seems to work, but also is quite hacky, I feel there must be a better way:
import builtins
from testfixtures import compare
@pytest.fixture(autouse=True)
def mock_open_file(self, mocker):
mocker.patch("builtins.open", mock_open(), create=True)
def assert_file_written_with_content(file_path, content):
"""When file is opened using builtins.open and this is mocked in the test, assert that the file is written with the given content."""
mock_open_file = builtins.open # This will be mock_open
for i, call in enumerate(mock_open_file.mock_calls[::4]):
current_iteration = i * 4
if call.args[0] == file_path and call.args[1] == "w":
call_write = mock_open_file.mock_calls[current_iteration + 2]
compare(content, call_write[1][0])
return
raise AssertionError(f"File {file_path} was not written with content {content}")
I'm a little confused by your code; in the second example you don't appear to be mocking out the
openmethod, and in the first example you have amock_open_filefixture but you don't appear to be using it.The following example demonstrates one way to assert on calls to the
writemethod; there's both a passing test and a failing test to demonstrate that it works as expected:It's also possible to assert on file content, rather than on calls to
write; this is useful if the content you're testing is created by multiplewritecalls. In this version of the code, we create anio.StringIO()object and use that as the return value ofopenso that we can test content afterwards using thegetvalue()method:Update Based on your comment, it sounds like the easiest solution would be to use the
pyfakefsmodule, like this:There's your
assert_content_written_to_filepathfunction. Thefsfixture provided bypyfakefstakes care of mocking all the filesystem-related functions, so despite appearance we're not actually writing (or reading) from the regular filesystem.