简体   繁体   中英

Python mocking function that isn't called directly

In my object, I have several "Loader" functions for different types of data that might require loading. They're stored as:

import foo
import bar

class MyClass
    LOADERS = {
        'foo': {
            'context_manager': False,
            'func': foo.data.loader,
            'kwargs': {'data_orientation': 'columns'}
            },
        'bar': {
            'context_manager': True,
            'func': bar.load_data
            'kwargs': {}
            }
        }

    def load_data(self, load_type, path):
        func = self.LOADERS[load_type]['func']
        kwargs = self.LOADERS[load_type]['kwargs']
        if self.LOADERS[load_type]['context_manager']:
            with open(path, 'rb') as f:
                self.data=func(f, **kwargs)
        else:
            self.data = func(path, **kwargs)

This is working very well in practice, but I'm finding it's murder to test .

When I write my test:

from mock import MagicMock, patch
import sys

sys.modules['foo'] = MagicMock()
sys.modules['bar'] = MagicMock()

from mypackage import MyClass

@patch('foo.data.loader')
def test_load_foo(mock_loader):
    my_obj = MyClass()
    my_obj.load_data('foo', 'path/to/data')
    mock_loader.assert_called_once()

it fails. Called 0 times.

I halfway suspect that it's because it's not being called directly. But that shouldn't really matter.

Can anyone offer any suggestions? I'm using pytest as my testing engine, but I've found it plays nicely with mock in the past.

Thanks!

The issue arises because you are trying to patch a function within a class attribute as opposed to an instance attribute. The behavior is not the same. This does however offer us some flexibility as it is a class attribute, thus we can modify the object directly on the class we instantiate in our test.

from unittest.mock import MagicMock

from mypackage import MyClass


def test_load_foo():
    key_to_test = "foo"
    mock_loader = MagicMock()
    
    my_obj = MyClass()
    my_obj.LOADERS[key_to_test]["func"] = mock_loader
    my_obj.load_data(key_to_test, 'path/to/data')
    mock_loader.assert_called_once()

Instead of trying to patch the function, we modify our objects instance to use the MagicMock .

Using this approach I receive the following message:

collected 1 item                                                                                                                                                

tests/test_mypackage.py .                                                                                                                                 [100%]

======================================================================= 1 passed in 0.01s =======================================================================

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM