pythontestingmockingpytestmonkeypatching

Mocking or monkey patching a slow loading resource that loads on import


I have 1 file which loads a particular resource ( from a file ) that takes a while to load.

I have a function in another file which uses that resource by importing it first. When that resource is first imported, it loads and takes a bit.

However, when I'm testing that function that requires that resource, I wanna pass it another smaller resource.

Unfortunately, there aren't any major refactors I'm able to do to either file1.py or file2.py. Is there a solution using pytest? I haven't been able to get it working.

file1.py

def load_resource():
    print("Loading big resource...")
    return {"key": "big resource"}

resource = load_resource()

file2.py

from file1 import resource

def my_func():
    return resource["key"]

test.py

import pytest

@pytest.fixture(autouse=True)
def mock_load_resource(monkeypatch):
    def mock_resource():
        return {'key': 'mocked_value'}
    monkeypatch.setattr('file1', 'load_resource', mock_resource)
    monkeypatch.setattr('file1', 'resource', mock_resource())

def test_my_function():
    from file2 import my_func
    result = my_func()
    assert result == 'mocked_value'

Solution

  • You cannot patch a global function without first importing that function, so the only way I know to achieve what you need is to mock the entire module. This can be done by replacing the module in sys.modules with your mock object:

    from unittest.mock import Mock, patch
    
    import pytest
    
    @pytest.fixture(autouse=True)
    def mock_load_resource():
        def mock_resource():
            return {'key': 'mocked_value'}
    
        fake_file1 = Mock()
        fake_file1.load_resource = mock_resource
        fake_file1.resource = mock_resource()
        with patch.dict('sys.modules', {'file1': fake_file1}):
            yield
    

    As far as I know, monkeypatch has no method that would allow that, but you can just use unittest.mock.patch.dict as shown above. This ensures that during the test, the mock will be loaded instead of the real test. After the test, the mock is removed from sys.modules, and the real module is loaded the next time it is imported.

    Of course, any other attribute or function inside the mocked module will also not work, so if you need more functionality from that module inside your test, you may have to do more mocking.