Search code examples
pythonmockingpython-mock

Mock file read method using size variable


I am trying to mock a file using the python mock library. Although simple enough, I still don't understand how to mock a read function when it must receive a size argument. I was trying to use the side_effect to create an alternative function, that would read just enough data passed as value.

This is the idea:

def mock_read(value):
    test_string = "abcdefghijklmnopqrs"

    '''
     Now it should read enough values from the test string, but 
     I haven't figured out a way to store the position where the
     "read" method has stopped.
    '''

mock_file = MagicMock(spec=file)
mock_file.read.side_effect = mock_read

However, I haven't figured out how to store the current position of the reader in the side_effect function, to read just after that. I think that there is maybe a better approach, but I still have figured it out yet.


Solution

  • Unfortunately mock_open doesn't support partial reads, moreover you use python 2.7 (I assume it because you write MagicMock(spec=file)) and mock_open is pretty limited.

    We can generalize your question like can we write side_effect that can hold state. There are some of ways to do it in python but IMHO the simplest is use a class that implement __call__ (generators cannot be used here because mock interpret generators like lists of side effects):

    from mock import MagicMock
    
    class my_read_side_effect():
        def __init__(self,data=""):
            self._data = data
        def __call__(self, l=0): #That make my_read_side_effect a callable
            if not self._data:
                return ""
            if not l:
                l = len(self._data)
            r, self._data = self._data[:l], self._data[l:]
            return r
    
    mock_file = MagicMock(spec=file)
    mock_file.read.side_effect = my_read_side_effect("abcdefghijklmnopqrs")
    assert "abcdef" == mock_file.read(6)
    assert "ghijklm" == mock_file.read(7)
    assert "nopqrs" == mock_file.read()
    

    Moreover we can inject that implementation in mock_open handler to patch mock_open.read() method.

    from mock patch, mock_open
    
    with patch("__builtin__.open", new_callable=mock_open) as mo:
        mock_file = mo.return_value
        mock_file.read.side_effect = my_read_side_effect("abcdefghijklmnopqrs")
        assert "abcdef" == mock_file.read(6)
        assert "ghijklm" == mock_file.read(7)
        assert "nopqrs" == mock_file.read()
    

    That give to you a simple way to use it in your test where file is open in the function and not passed as argument.