I am not trying to do anything practical here; I am just trying to understand how to use the unittest.mock
module in this particular use case. What I am attempting to do here is so simple, straightforward, and so smack down the middle of unitttest.mock
's wheelhouse that I can't believe I am having such a hard time getting it to work.
This toy example shows the problem:
def fn(in1, in2, out):
with open(in1) as f1, open(in2) as f2, open(out, 'w') as f3:
for line in f1:
f3.write(f'1>{line}')
for line in f2:
f3.write(f'2>{line}')
from unittest.mock import patch, mock_open
from io import StringIO
in1 = StringIO('a\nb\n')
in2 = StringIO('c\nd\ne\n')
out = StringIO()
expected = '1>a\n1>b\n2>c\n2>d\n2>e\n'
expected_first_line = expected.splitlines()[0] + '\n'
with patch('builtins.open', new_callable=mock_open) as fake_open:
fake_open.side_effect = [in1, in2, out]
fn('this', 'that', 'somethingelse')
Q: I want to know how must I modify above script so that I can test whether or not fn
wrote to the write handle the string stored in expected
1.
I have tried to use the answers given in
How do I mock an open(...).write() without getting a 'No such file or directory' error?
...but they don't work at all.
For example, if I attempt to follow the first answer in the post above, by adding the following lines at the end of the script above:
handle = fake_open()
handle.write.assert_called_once_with(expected_first_line)
...the assertion never runs because the assignment on the first line bombs:
Traceback (most recent call last):
File "/tmp/try.py", line 21, in <module>
handle = fake_open()
File "/opt/python3/3.8.0/lib/python3.8/unittest/mock.py", line 1075, in __call__
return self._mock_call(*args, **kwargs)
File "/opt/python3/3.8.0/lib/python3.8/unittest/mock.py", line 1079, in _mock_call
return self._execute_mock_call(*args, **kwargs)
File "/opt/python3/3.8.0/lib/python3.8/unittest/mock.py", line 1136, in _execute_mock_call
result = next(effect)
StopIteration
If I follow the second answer, and add this line
fake_open.return_value.__enter__().write.assert_called_once_with(expected_first_line)
...the test fails with
AssertionError: Expected 'write' to be called once. Called 0 times.
...which is a bit more civilized, but still pretty hard for me to understand.
Also, it appears that, once the function returns, the out
variable can no longer be read. (Every operation I've tried on out
fails with a ValueError: I/O operation on closed file
exception.)
Of course, I realize that I can roll my own mock object in 10-20 lines of code, but what I am trying to do here should be bread-and-butter for unittest.mock
.
1Even though in some of my attempts I used the value in expected_first_line
, I am really not interested in testing just one line in the output, but all of it. I hope that there is a straightforward way to do this.