On Monday, March 12, 2018 at 4:51:53 AM UTC-4, Tim Golden wrote:
> I'm contributing to a codebase which makes heavy use of mock in the test
> suite, a technique which I'm aware of but have used only rarely. In one
> situation it uses mock.mock_open(read_data="...") and then asserts again
> A code change I've made results in an increase in the call count but
> also the open() I've introduced opens the file in binary mode and does
> something with the resulting data.
> Hugely simplified, the new code and unchanged test looks like this:
> import os, sys
> import unittest
> from unittest import mock
> def read_file(filename):
> # This section is new
> with open(filename, "rb") as f:
> text = f.read()
> if text.startswith(b"#"):
> with open(filename) as f:
> text = f.read()
> if text.startswith("#"):
> return text
> class TestS(unittest.TestCase):
> def test_read_file(self):
> mock_open = mock.mock_open(read_data="abc")
> with mock.patch('builtins.open', mock_open):
> data = read_file("abc")
> assert mock_open.return_value.read.call_count == 1
> if __name__ == '__main__':
> I would expect the test to fail because of the call_count change. But in
> fact it errors out because the newly-added "if test.startswith()"
> receives a string, not bytes, from the Mock's read_data functionality.
> Ignore for the moment any question of changing the read_file
> implementation to assist testing. And leave aside the question of
> whether a mock_open is really a good test approach here.
> Is there any way in which I can have the mock_open object return bytes
> for the first open and a string for the second? I've looked at setting a
> side_effect function against the mock_open.return_value.read Mock, but I
> can't see a way of having the function know whether it's supposed to be
> returning bytes or string.
2 ways come to mind:
1. Have the side effect function check something that differentiates
those 2 calls. In this case, checking mode argument value should work.
2. Rely on the order of calls with a local side effect function
closing over a call number. Something like this (untested):
call_number = 0
def open_side_effect(filename, mode='r'):
call_number += 1
if call_number == 1:
return b'some bytes'
if call_number == 2:
return 'some string'
with mock.patch(..., side_effect=open_side_effect):