How to retrieve all the content of calls made to a mock?
Question:
I’m writing a unit test for a function that takes an array of dictionaries and ends up saving it in a CSV. I’m trying to mock it with pytest as usual:
csv_output = (
"NametSurnamern"
"EvetFirstrn"
)
with patch("builtins.open", mock_open()) as m:
export_csv_func(array_of_dicts)
assert m.assert_called_once_with('myfile.csv', 'wb') is None
[and here I want to gather all output sent to the mock "m" and assert it against "csv_output"]
I cannot get in any simple way all the data sent to the mock during the open() phase by csv
to do the comparison in bulk, instead of line by line. To simplify things, I verified that the following code mimics the operations that export_csv_func()
does to the mock:
with patch("builtins.open", mock_open()) as m:
with open("myfile.csv", "wb") as f:
f.write("NametSurnamern")
f.write("EvetFirstrn")
When I dig into the mock, I see:
>>> m
<MagicMock name='open' spec='builtin_function_or_method' id='4380173840'>
>>> m.mock_calls
[call('myfile.csv', 'wb'),
call().__enter__(),
call().write('NametSurnamern'),
call().write('EvetFirstrn'),
call().__exit__(None, None, None)]
>>> m().write.mock_calls
[call('NametSurnamern'), call('EvetFirstrn')]
>>> dir(m().write.mock_calls[0])
['__add__'...(many methods), '_mock_from_kall', '_mock_name', '_mock_parent', 'call_list', 'count', 'index']
I don’t see anything in the MagickMock interface where I can gather all the input that the mock has received.
I also tried calling m().write.call_args
but it only returns the last call (the last element of the mock_calls
attribute, i.e. call('EvetFirstrn')
).
Is there any way of doing what I want?
Answers:
Indeed you can’t patch builtins.open.write
directly since the patch within a with would need to enter the patched method and see that write
is not a class method.
There are a bunch of solutions and the one I would think of first would be to use your own mock. See the example:
class MockOpenWrite:
def __init__(self, *args, **kwargs):
self.res = []
# What's actually mocking the write. Name must match
def write(self, s: str):
self.res.append(s)
# These 2 methods are needed specifically for the use of with.
# If you mock using a decorator, you don't need them anymore.
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
return
mock = MockOpenWrite
with patch("builtins.open", mock):
with open("myfile.csv", "w") as f:
f.write("NametSurnamern")
f.write("EvetFirstrn")
print(f.res)
In that case, the res attribute is linked to the instance. So it disappears after the with
closes.
You could eventually stored results somewhere else, like a global array, and check the results beyond the end of with
.
Feel free to play around with your actual method.
You can create your own mock.call
objects and compare them with what you have in the .call_args_list
.
from unittest.mock import patch, mock_open, call
with patch("builtins.open", mock_open()) as m:
with open("myfile.csv", "wb") as f:
f.write("NametSurnamern")
f.write("EvetFirstrn")
# Create your array of expected strings
expected_strings = ["NametSurnamern", "EvetFirstrn"]
write_calls = m().write.call_args_list
for expected_str in expected_strings:
# assert that a mock.call(expected_str) exists in the write calls
assert call(expected_str) in write_calls
Note that you can use the assert call of your choice. If you’re in a unittest.TestCase subclass, prefer to use self.assertIn
.
Additionally, if you just want the arg values you can unpack a mock.call
object as tuples. Index 0 is the *args. For example:
for write_call in write_calls:
print('args: {}'.format(write_call[0]))
print('kwargs: {}'.format(write_call[1]))
I had to it this way (Python 3.9). It was quite tedious just to get the mock-args out of the function.
from somewhere import my_thing
@patch("lib.function", return_value=MagicMock())
def test_my_thing(my_mock):
my_thing(value1, value2)
(value1_call_args, value2_call_args) = my_mock.call_args_list[0].args
I’m writing a unit test for a function that takes an array of dictionaries and ends up saving it in a CSV. I’m trying to mock it with pytest as usual:
csv_output = (
"NametSurnamern"
"EvetFirstrn"
)
with patch("builtins.open", mock_open()) as m:
export_csv_func(array_of_dicts)
assert m.assert_called_once_with('myfile.csv', 'wb') is None
[and here I want to gather all output sent to the mock "m" and assert it against "csv_output"]
I cannot get in any simple way all the data sent to the mock during the open() phase by csv
to do the comparison in bulk, instead of line by line. To simplify things, I verified that the following code mimics the operations that export_csv_func()
does to the mock:
with patch("builtins.open", mock_open()) as m:
with open("myfile.csv", "wb") as f:
f.write("NametSurnamern")
f.write("EvetFirstrn")
When I dig into the mock, I see:
>>> m
<MagicMock name='open' spec='builtin_function_or_method' id='4380173840'>
>>> m.mock_calls
[call('myfile.csv', 'wb'),
call().__enter__(),
call().write('NametSurnamern'),
call().write('EvetFirstrn'),
call().__exit__(None, None, None)]
>>> m().write.mock_calls
[call('NametSurnamern'), call('EvetFirstrn')]
>>> dir(m().write.mock_calls[0])
['__add__'...(many methods), '_mock_from_kall', '_mock_name', '_mock_parent', 'call_list', 'count', 'index']
I don’t see anything in the MagickMock interface where I can gather all the input that the mock has received.
I also tried calling m().write.call_args
but it only returns the last call (the last element of the mock_calls
attribute, i.e. call('EvetFirstrn')
).
Is there any way of doing what I want?
Indeed you can’t patch builtins.open.write
directly since the patch within a with would need to enter the patched method and see that write
is not a class method.
There are a bunch of solutions and the one I would think of first would be to use your own mock. See the example:
class MockOpenWrite:
def __init__(self, *args, **kwargs):
self.res = []
# What's actually mocking the write. Name must match
def write(self, s: str):
self.res.append(s)
# These 2 methods are needed specifically for the use of with.
# If you mock using a decorator, you don't need them anymore.
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
return
mock = MockOpenWrite
with patch("builtins.open", mock):
with open("myfile.csv", "w") as f:
f.write("NametSurnamern")
f.write("EvetFirstrn")
print(f.res)
In that case, the res attribute is linked to the instance. So it disappears after the with
closes.
You could eventually stored results somewhere else, like a global array, and check the results beyond the end of with
.
Feel free to play around with your actual method.
You can create your own mock.call
objects and compare them with what you have in the .call_args_list
.
from unittest.mock import patch, mock_open, call
with patch("builtins.open", mock_open()) as m:
with open("myfile.csv", "wb") as f:
f.write("NametSurnamern")
f.write("EvetFirstrn")
# Create your array of expected strings
expected_strings = ["NametSurnamern", "EvetFirstrn"]
write_calls = m().write.call_args_list
for expected_str in expected_strings:
# assert that a mock.call(expected_str) exists in the write calls
assert call(expected_str) in write_calls
Note that you can use the assert call of your choice. If you’re in a unittest.TestCase subclass, prefer to use self.assertIn
.
Additionally, if you just want the arg values you can unpack a mock.call
object as tuples. Index 0 is the *args. For example:
for write_call in write_calls:
print('args: {}'.format(write_call[0]))
print('kwargs: {}'.format(write_call[1]))
I had to it this way (Python 3.9). It was quite tedious just to get the mock-args out of the function.
from somewhere import my_thing
@patch("lib.function", return_value=MagicMock())
def test_my_thing(my_mock):
my_thing(value1, value2)
(value1_call_args, value2_call_args) = my_mock.call_args_list[0].args