Python is pretty literal minded. The expression pg.connect()
translates to:
- Look up the name
pg
- Look up the attribute
connect
on that.
- Call it with empty arguments.
So an expression pg.connect
will obtain the method, but not call it. And, in fact, you can use that method as a regular value and pass it around, so my_connect = pg.connect
is entirely valid, and you can later call my_connect()
.
We can see what's going on with the dis
module:
import dis
dis.dis(lambda: pg.connect())
1 0 LOAD_GLOBAL 0 (pg)
2 LOAD_ATTR 1 (connect)
4 CALL_FUNCTION 0
6 RETURN_VALUE
Since Mock
wants to pretend to be different objects, it is overriding the standard __getattribute__
and __call__
and __eq__
dunder methods. While objects have a normal way of providing attributes, __getattribute__
lets a class do whatever it wants if an attribute is requested.
When you call it as Mock(spec=psycopg2)
, it's going to do two main things differently than Mock()
:
- It adjusts its
__class__
field to claim to be psycopg2
.
- Check psycopg2 for valid properties and raise
AttributeError
if you try to get a field that isn't present.
But beyond that, it still behaves as a regular mock. If you call yourMock.foo
, it doesn't know if it's a property or a method. It just returns a new Mock
instance, and because that instance is callable, you can call it like a function.
Update: If you set spec on Mock
, it's restricting the available attributes. To add an attribute not provided by spec, you just need to set it at init time:
cursor_mock = Mock(side_effect=psycopg2.DatabaseError)
Mock(spec=psycopg2.connect, cursor=cursor_mock)
You also wanted this behavior: pg.connect().cursor().execute.side_effect = someexception()
. Generally, you'd alter the return_value
:
cursor_mock = Mock()
cursor_mock.return_value.execute.side_effect = SomeException
# Same as:
cursor_mock = Mock(return_value=Mock(execute=Mock(side_effect=SomeException)))
You can also do cursor_mock().execute.side_effect = SomeException
, but that pollutes cursor_mock.mock_calls
.
Small point: I use Mock
for brevity, and MagicMock
is the same, simply adding some dunder methods.