Search code examples
pythonmockingpathlib

Mock/Test Calls to Path.open


I am attempting to write a unit test for a function that calls the open method on a pathlib.Path. I am able to successfully mock the open method without issue, but verifying the function is having the correct behavior is difficult. See the sample code below:

def test_my_function(self):
    with patch.object(Path, 'open') as mock_open:
        my_function(*args)  # This function calls Path.open

When I introspect mock_open and review the _mock_mock_calls list, I am unable to find the string path of the file that is being written to. The call history looks like this:

[
    call(mode='w'),
    call().__enter__(),
    call().__enter__().write('<file contents>'),
    call().__enter__().flush(),
    call().__exit__(None, None, None),
]

Is there a way to test what path is being opened when Path.open is called?


Solution

  • You replaced a method with a mock object. The issue with using a mock object here is that it won't be bound to the Path() instance. It'll be called, but there is no path back to the Path() instance (no pun intended).

    Use a function to mock out open(), one that returns a mock_open() object to track further 'open file' use, functions will be bound when accessed on instances of Path:

    from unittest.mock import patch, mock_open
    
    def test_my_function(self):
        opener = mock_open()
        def mocked_open(self, *args, **kwargs):
            return opener(self, *args, **kwargs)
        with patch.object(Path, 'open', mocked_open):
            my_function(*args)  # This function calls Path.open
    

    Now any Path().open() call will call the opener mock, recording all file interactions and the Path() object on which it was called:

    >>> from pathlib import Path
    >>> from unittest.mock import patch, mock_open
    >>> opener = mock_open()
    >>> def mocked_open(self, *args, **kwargs):
    ...     return opener(self, *args, **kwargs)
    ...
    >>> with patch.object(Path, 'open', mocked_open):
    ...     print(Path.open)
    ...     print(Path().open)
    ...     with Path().open() as f:
    ...         f.write('<file contents>')
    ...         f.flush()
    ...
    <function mocked_open at 0x12026f5c0>
    <bound method mocked_open of PosixPath('.')>
    <MagicMock name='open().flush()' id='4834728928'>
    >>> opener.mock_calls
    [call(PosixPath('.')),
     call().__enter__(),
     call().write('<file contents>'),
     call().flush(),
     call().__exit__(None, None, None)]