Mock Open() Function Used In A Class Method
Solution 1:
TL;DR
The heart of your problem is that you should be also mocking json.dump
to be able to properly test the data that is going to be written to your file. I actually had a hard time running your code until a few important adjustments were made to your test method.
- Mock with
builtins.open
and notmymmodule.open
- You are in a context manager, so you should be checking
m.return_value.__enter__.write
, however you are actually calling the write from json.dump which is where the write will be called. (Details below on a suggested solution) - You should also mock
json.dump
to simply validate it is called with your data
In short, with the issues mentioned above, the method can be re-written as:
Details about all this below
deftest_save_data_to_file(self):
with patch('builtins.open', new_callable=mock_open()) as m:
with patch('json.dump') as m_json:
self.mc.save_data_to_file(self.data)
# simple assertion that your open was called
m.assert_called_with('/tmp/data.json', 'w')
# assert that you called m_json with your data
m_json.assert_called_with(self.data, m.return_value)
Detailed Explanation
To focus on the problems I see in your code, the first thing I strongly suggest doing, since open
is a builtin, is to mock from builtins, furthermore, you can save yourself a line of code by making use of new_callable
and as
, so you can simply do this:
withpatch('builtins.open', new_callable=mock_open()) as m:
The next problem that I see with your code as I had trouble running this until I actually made the following adjustment when you started looping over your calls:
m.return_value.__enter__.return_value.write.mock_calls
To dissect that, what you have to keep in mind is that your method is using a context manager. In using a context manager, the work of your write will actually be done inside your __enter__
method. So, from the return_value
of your m
, you want to then get the return_value of __enter__
.
However, this brings us to the heart of the problem with what you are trying to test. Because of how the json.dump
works when writing to the file, your mock_calls
for your write after inspecting the code, will actually look like this:
<MagicMock name='open().write' id='4348414496'>call('[')
call('{')
call('"name"')
call(': ')
call('"peter"')
call(', ')
call('"id"')
call(': ')
call('5414470')
call('}')
call(', ')
call('{')
call('"name"')
call(': ')
call('"tom"')
call(', ')
call('"id"')
call(': ')
call('5414472')
call('}')
call(', ')
call('{')
call('"name"')
call(': ')
call('"pit"')
call(', ')
call('"id"')
call(': ')
call('5414232')
call('}')
call(']')
call.__str__()
That is not going to be fun to test. So, this brings us to the next solution you can try out; Mock json.dump
.
You shouldn't be testing json.dump, you should be testing calling it with the right parameters. With that being said, you can follow similar fashion with your mocking and do something like this:
withpatch('json.dump') as m_json:
Now, with that, you can significantly simplify your test code, to simply validate that the method gets called with your data that you are testing with. So, with that, when you put it all together, you will have something like this:
deftest_save_data_to_file(self):
with patch('builtins.open', new_callable=mock_open()) as m:
with patch('json.dump') as m_json:
self.mc.save_data_to_file(self.data)
# simple assertion that your open was called
m.assert_called_with('/tmp/data.json', 'w')
# assert that you called m_json with your data
m_json.assert_called_with(self.data, m.return_value.__enter__.return_value)
If you're interested in further refactoring to make your test method a bit cleaner, you could also set up your patching as a decorator, leaving your code cleaner inside the method:
@patch('json.dump')@patch('builtins.open', new_callable=mock_open())deftest_save_data_to_file(self, m, m_json):
self.mc.save_data_to_file(self.data)
# simple assertion that your open was called
m.assert_called_with('/tmp/data.json', 'w')
# assert that you called m_json with your data
m_json.assert_called_with(self.data, m.return_value.__enter__.return_value)
Inspecting is your best friend here, to see what methods are being called at what steps, to further help with the testing. Good luck.
Post a Comment for "Mock Open() Function Used In A Class Method"