Adam Adam - 2 months ago 16
Python Question

Reusing Mock to create attribute mocks unittest.patch

from unittest.mock import patch


class A:
def b(self):
return 'HAHAHA A'
a = A()

with patch('__main__.A') as a_mock:
a_mock.b.return_value = 'not working'
print(a.b())

HAHAHA A
>>>


Why doesn't it print
'not working'
? What for is a_mock, then? ______________

Answer

You replaced the whole class with your patch, not the method on the existing class. a = A() created an instance of A before you replaced the class, so a.__class__ still references the actual class, not the mock.

Mocking can only ever replace one reference at a time, and not the object referenced. Before the patch, both the names A and the attribute a.__class__ are references to the class object. You then patched only the A reference, leaving a.__class__ in place.

In other words, a.__class__ is not patched, only A, and A().b() would print not working.

You'd have to patch just the method on the class, so that a.__class__ still references A, and a.b will resolve to the patched A.b mock:

with patch('__main__.A.b') as b_mock:
    b_mock.return_value = 'working as long as you patch the right object'
    print(a.b())

Demo:

>>> with patch('__main__.A.b') as b_mock:
...     b_mock.return_value = 'working as long as you patch the right object'
...     print(a.b())
...
working as long as you patch the right object

You can't, unfortunately, patch the a.__class__ reference with a Mock; Python only lets you use actual classes for that attribute.

>>> with patch('__main__.a.__class__') as a_class_mock:
...     a_class_mock.b.return_value = 'working as long as you patch the right object'
...     print(a.b())
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/mjpieters/Development/Library/buildout.python/parts/opt/lib/python3.5/unittest/mock.py", line 1312, in __enter__
    setattr(self.target, self.attribute, new_attr)
TypeError: __class__ must be set to a class, not 'MagicMock' object
Comments