'MagicMock and monkeypatch questions - mock objects lose properties when monkeypatching them

I am trying to mock the content of a file that I read from an sFTP server. Here is the method I want to test, simplified:

def test_target():
    try:
        fs = SFTPFileSystem(...)
        objs = listFolder(fs, "/some_folder")
        for obj in objs:
            with fs.open(obj["name"]) as f:
                file_content = f.read()
                ... process file_content
    finally:
        if fs:
            fs.ftp.close()
          

So I started with this approach:

In [12]: mock_fs = MagicMock()
In [13]: mock_fs.open.return_value = io.BytesIO("Hello World".encode("utf-8"))  

Works:

In [14]: mock_fs.open().read()
Out[14]: b'Hello World'

However, when I monkeypatch with this mocked object:

monkeypatch.setattr(my_app.my_module, "SFTPFileSystem", mock_fs)

calls to open() in my_module return a MagicMock, not my byte stream. file_content is reported as <MagicMock name='SFTPFileSystem().open().__enter__().read()' id='...'>

It looks like by calling setattr() it becomes a different instance that does not have the return value set anymore. Why is that?

Similarily, using patch()

with patch("my_app.my_module.SFTPFileSystem", spec=True) as m:
    m.ftp = MagicMock()

    test_target()

This will throw an exception when I call a method on ftp of my SFTPFileSystem instance in the test target:

 E               AttributeError: Mock object has no attribute 'ftp'

Oddly, fs is

<NonCallableMagicMock name='SFTPFileSystem()' spec='SFTPFileSystem' id='139762783658544'>.

Why the non-callable form?

What always works reliably is monkeypatching methods in my_module. This has been my work around. I can put each piece of functionality that I need to patch into a separate method, then I can override it. But this approach requires that I refactor my code, which I'd rather avoid.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source