Search code examples
pythonpython-mock

Why mock.patch works on attribute of object that is already imported


I am aware of the importance of path to mock as illustrated here, but consider this Django scenario

models.py

class Proxmox(Model):
    @property
    def api(self, ...):
        ....

tasks.py

def run_task(...):
    ....

views.py

from models import Proxmox
from tasks import run_task

class APIView(...):
    def get(request):
        Proxmox.objects.get(...).api.do_something()
        run_task()

tests.py

class MyTestCase(...):
    @mock.patch('tasks.run_task')     <---- this is not patched as already imported in view
    #@mock.patch('views.run_task').   <---- this patches fine
    @mock.patch('models.Proxmox.api') <---- but why this works fine?
    def test_one(self, mock_api, mock_run_task):
        client.get(....)
        ...

Both Proxmox and run_task are imported into views.py so why patching models.Proxmox.api still got patched?


Solution

  • In short, it's because "api" is looked up in the namespace of a type, rather than in the namespace of a module.

    What matters with patching is how/where a name is looked up, not where it is defined. In this case the name "api" is looked up on the type Proxmox, regardless of which module namespace the name Proxmox is found in.

    Here mock patches the class namespace of Proxmox, replacing the property descriptor.

    It works because, no matter where where the type Proxmox is imported from, accessing Proxmox.api will always be an attribute access on the Proxmox class. All module namespaces will see that same patch, because they all point to the same identical class type.