pythonpython-3.xpython-unittestpython-unittest.mock

Difference between mock.patch.dict as function decorator and context manager in Python unittest


I have a configuration module, config with data config.data where I want to add a value for testing. Ideally, I want to use mock.patch.dict as a context manager because the value is a class attribute and I need to use self.test_value. The problem is it doesn't work. The dict patching only works when used as a decorator, but not when used as a context manager and I can't figure out why.

This is more or less what I have:

This works:

    @mock.patch.dict("config.data", {"test": 123})
    @mock.patch("config.load_data", return_value=None, autospec=True)
    def run(self, m_load, *args, **kwargs):
        return super().run(*args, **kwargs)

This doesn't work:

    def run(self, *args, **kwargs):
        with mock.patch.dict("config.data", {"test": 123}) and mock.patch(
                "config.load_data", return_value=None, autospec=True
            ) as m_load:
                return super().run(*args, **kwargs)

The config.load_data is patched so the data doesn't reload, and the module is sort of a singleton.


Solution

  • You are running in Python issue, how with statement works with and. In your case only the second context manager is being properly entered and dict patch is not managed in correct way. So to resolve it just use more with statements separated by commas, not and. Or just rewrite your patches indentation.

    def run(self, *args, **kwargs):
        with mock.patch.dict("config.data", {"test": 123}), \
             mock.patch("config.load_data", return_value=None, autospec=True) as m_load:
            return super().run(*args, **kwargs)
    

    OR

    def run(self, *args, **kwargs):
        with mock.patch.dict("config.data", {"test": 123}):
            with mock.patch("config.load_data", return_value=None, autospec=True):
                return super().run(*args, **kwargs)
    

    Also about your next question "what's the difference then between using ` & and?"

    So and doesn't mean that it should be used in both context managers. Instead mock.patch.dict is evaluated, but not entered as context YET. Then and check its truth. And if its true, mock.patch(..) evaluated and entered as REAL context manager. So only the second patch works correctly.

    And when you use commas or with, python will enter mock.patch.dict(..), then mock.patch(..) and exit. Its likely that @mock.patch decorators do deeply.

    with cm1:
        with cm2:
            ...
    
    with cm1, cm2: