Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 663
Adam Avatar asked Mar 14 '19 14:03

Adam


People also ask

How do I open a mock file?

Basically, open() will return an object and with will call __enter__() on that object. To mock properly, we must mock open() to return a mock object. That mock object should then mock the __enter__() call on it ( MagicMock will do this for us) to return the mock data/file object we want (hence mm. __enter__.

What is Side_effect in mock Python?

side_effect: A function to be called whenever the Mock is called. See the side_effect attribute. Useful for raising exceptions or dynamically changing return values. The function is called with the same arguments as the mock, and unless it returns DEFAULT , the return value of this function is used as the return value.

What is MagicMock?

To begin with, MagicMock is a subclass of Mock . class MagicMock(MagicMixin, Mock) As a result, MagicMock provides everything that Mock provides and more. Rather than thinking of Mock as being a stripped down version of MagicMock, think of MagicMock as an extended version of Mock.


1 Answers

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)]
like image 65
Martijn Pieters Avatar answered Sep 18 '22 20:09

Martijn Pieters