lajd
lajd

Reputation: 105

How do I patch a function imported by an imported class?

I am writing python unittests and need to patch a function from a class not explicitly imported at the call site. I encounter an error unless I add the import (unused) to the file the call is from. I would like to know if there is a way around this unused import.

I have created an example with the same issue:

test_1.py

class Test1():

    def test_1_func(self, arg):
        print(f"This is test_1_func, called with {arg}")

test_2.py

from test_1 import Test1

class Test2():

    def __init__(self):
        self.test1 = Test1()

test_3.py

from test_2 import Test2

class Test3():

    def __init__(self):
        self.test2=Test2()

    def test_3_func(self):
        print("This is test_3_func")
        self.test2.test1.test_1_func("ARGUMENT")

tests.py

import unittest
from mock import patch

from test_3 import Test3

class MyTestCase(unittest.TestCase):

    @patch('test_3.Test1.test_1_func')
    def test_test_3(self, mock_test_1_func):
        Test3().test_3_func()
        mock_test_1_func.assert_called_with("ARGUMENT")

When test_test_3 is run, I get the error:

ModuleNotFoundError: No module named 'test_3.Test1'; 'test_3' is not a package

However, when I add Test1 as an import in test_3.py (as below) the test completes without error.

from test_1 import Test1
from test_2 import Test2

class Test3():

    def __init__(self):
        self.test2=Test2()

    def test_3_func(self):
        print("This is test_3_func")
        self.test2.test1.test_1_func("ARGUMENT")

Upvotes: 1

Views: 2505

Answers (1)

gilch
gilch

Reputation: 11651

You should still be able to use the absolute path:

@patch('test_1.Test1.test_1_func')

I am a little curious as to why it works, I thought the patch had to be at the call site

It's enough that the location it eventually gets read from is swapped out for the mock. Think about the structure of the object graph. Most Python objects have a __dict__ for their attributes (unless this has been specifically turned off), including classes and modules. The test_1 module has a reference to the Test1 class, which has a reference to the test_1_func function. That last reference Test1.__dict__['test_1_func'] is what gets patched.

The usage path is self.test2.test1.test_1_func. That last link is test1 (an instance of the Test1 class) to test_1_func. When an instance object can't find an attribute in its __dict__, it will look for it in its class's __dict__, which is where the patch occurred, so it returns the mock.

(I'm simplifying a little bit here, since functions are descriptors, this overrides their attribute access and returns them as bound methods instead, but it still uses the mock.)

Upvotes: 1

Related Questions