Would the same chain of calls on a mock always return the exact same mock object?

Lon*_*ner 1 python unit-testing monkeypatching mocking

Code:

from unittest.mock import Mock

mock = Mock()

print('mock.f():', id(mock.f()))
print('mock.f().g().h():', id(mock.f().g().h()))
print('mock():', id(mock()))
print('mock().f():', id(mock().f()))
print()

print('mock.f():', id(mock.f()))
print('mock.f().g().h():', id(mock.f().g().h()))
print('mock():', id(mock()))
print('mock().f():', id(mock().f()))
print()

print('mock.f(1):', id(mock.f(1)))
print('mock.f(2).g(3).h(4):', id(mock.f(2).g(3).h(4)))
print('mock(5):', id(mock(5)))
print('mock(6).f(7):', id(mock(6).f(7)))
print()
Run Code Online (Sandbox Code Playgroud)

Output:

mock.f(): 4483288208
mock.f().g().h(): 4483354192
mock(): 4483368976
mock().f(): 4483708880

mock.f(): 4483288208
mock.f().g().h(): 4483354192
mock(): 4483368976
mock().f(): 4483708880

mock.f(1): 4483288208
mock.f(2).g(3).h(4): 4483354192
mock(5): 4483368976
mock(6).f(7): 4483708880
Run Code Online (Sandbox Code Playgroud)

Observation:

The output shows that a specified chained function call on a mock always returns the same object within the lifetime of a program regardless of how many times we make that call.

For example, the first call to mock.f().g().h(), the second call to mock.f().g().h(), and even the third call with different arguments mock.f(2).g(3).h(4) return the exact same object.

Question:

  • Can we rely on this behavior? Is it guaranteed that within the lifetime of a program, mock.f().g().h() would return the exact same mock object?
  • Is it guaranteed that even the same chain of calls with different arguments, e.g., mock.f(2).g(3).h(4) would also return the same object as a mock.f().g().h()?
  • Are both these things documented somewhere?

Background:

The reason why I am asking this is so that instead of writing code like this:

mock.f(): 4483288208
mock.f().g().h(): 4483354192
mock(): 4483368976
mock().f(): 4483708880

mock.f(): 4483288208
mock.f().g().h(): 4483354192
mock(): 4483368976
mock().f(): 4483708880

mock.f(1): 4483288208
mock.f(2).g(3).h(4): 4483354192
mock(5): 4483368976
mock(6).f(7): 4483708880
Run Code Online (Sandbox Code Playgroud)

I can write code like this insted:

from urllib import request
from unittest.mock import Mock, patch

with patch('urllib.request.urlopen') as mock_urlopen:
    mock_urlopen.return_value = Mock()
    mock_urlopen().getcode.return_value = 200
    assert request.urlopen('').getcode() == 200
Run Code Online (Sandbox Code Playgroud)

The examples above are too simple only for demo purpose. I wanted to keep self-contained examples. But if I could rely on this feature, it would become very convenient when the chain of function calls are long. That's why I am looking for some sort of reference or documentation that shows that I can rely on this behavior.

Tar*_*ani 5

如果你看文件 lib/python3.7/unittest/mock.py

def __getattr__(self, name):
    if name in {'_mock_methods', '_mock_unsafe'}:
        raise AttributeError(name)
    elif self._mock_methods is not None:
        if name not in self._mock_methods or name in _all_magics:
            raise AttributeError("Mock object has no attribute %r" % name)
    elif _is_magic(name):
        raise AttributeError(name)
    if not self._mock_unsafe:
        if name.startswith(('assert', 'assret')):
            raise AttributeError(name)

    result = self._mock_children.get(name)
    if result is _deleted:
        raise AttributeError(name)
    elif result is None:
        wraps = None
        if self._mock_wraps is not None:
            # XXXX should we get the attribute without triggering code
            # execution?
            wraps = getattr(self._mock_wraps, name)

        result = self._get_child_mock(
            parent=self, name=name, wraps=wraps, _new_name=name,
            _new_parent=self
        )
        self._mock_children[name]  = result

    elif isinstance(result, _SpecState):
        result = create_autospec(
            result.spec, result.spec_set, result.instance,
            result.parent, result.name
        )
        self._mock_children[name]  = result

    return result
Run Code Online (Sandbox Code Playgroud)

如您所见,对象在_mock_childrendict中缓存。因此,每次调用都会返回该对象。但是数据将被更新。通过运行以下代码可以看到

from unittest.mock import Mock

mock = Mock()

mock.a(10)
mock.a.assert_called_with(10)
mock.a(2)
mock.a.assert_called_with(10)
Run Code Online (Sandbox Code Playgroud)

和结果

Traceback (most recent call last):
  File ".../workbench.py", line 8, in <module>
    mock.a.assert_called_with(10)
  File ....lib/python3.7/unittest/mock.py", line 834, in assert_called_with
    raise AssertionError(_error_message()) from cause
AssertionError: Expected call: a(10)
Actual call: a(2)
Run Code Online (Sandbox Code Playgroud)

因此,是的,对象将是相同的,但是对象将具有更新后的值