A mock will let you call any method or read any attribute, even if it doesn’t exist.

>>> x = Mock()

>>> wtf = x.wtf()
>>> type(wtf)
<class 'unittest.mock.Mock'>
>>> wtf2 = x.wtf()
>>> wtf == wtf2
True

The returned values are consistent across references.

>>> omai = x.omai
>>> type(omai)
<class 'unittest.mock.Mock'>
>>> omai2 = x.omai
>>> omai == omai2
True

If you assign a value to an attribute, the mock is replaced.

>>> x.omai = 'replacement'
>>> x.omai
'replacement'

Mocked functions return mocks. They always return the same value.

>>> wtf() == wtf()
True
>>> wtf() == wtf2()
True

You can replace the return value. The backing mock is a singleton, so it is updated everywhere.

>>> wtf.return_value = 666
>>> wtf()
666
>>> wtf2()
666

Mock names are auto-generated and can be used to see how the mock was created/referenced.

>>> y = Mock()
>>> y
<Mock id='140403021814864'>
>>> y.hello
<Mock name='mock.hello' id='140402753195408'>
>>> y.hello.shirley
<Mock name='mock.hello.shirley' id='140402618536016'>
>>> y.hello().shirley.what.are.you().doing
<Mock name='mock.hello().shirley.what.are.you().doing' id='140402754691600'>

You can restrict the mock to specific attributes with spec.

>>> z = Mock(spec=['color', 'flavor', 'diarrhea'])
>>> z.color
<Mock name='mock.color' id='140402618331216'>
>>> z.flavor
<Mock name='mock.flavor' id='140402485559120'>
>>> z.diarrhea
<Mock name='mock.diarrhea' id='140402618142224'>
>>> z.horse_sense
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    z.horse_sense
  File "/opt/anaconda3/envs/pi/lib/python3.7/unittest/mock.py", line 599, in __getattr__
    raise AttributeError("Mock object has no attribute %r" % name)
AttributeError: Mock object has no attribute 'horse_sense'

Mocks do not have dunder methods.

>>> a = Mock()
>>> with a as a_context:
...     print(a_context)
...
...
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    with a as a_context:
AttributeError: __enter__
>>> b = Mock()
>>> b.__enter__ = Mock()
>>> b.__exit__ = Mock()
>>> with b as b_context:
...     print(b_context)
...
...
<Mock name='mock.__enter__()' id='140402757814160'>

MagicMocks do have dunder methods.

>>> c = MagicMock()
>>> with c as c_context:
...     print(c_context)
...
...
<MagicMock name='mock.__enter__()' id='140402757693904'>