Ankit
Ankit

Reputation: 4720

How to mock.patch a class imported in another module

I have a python class with such a module:

xy.py

from a.b import ClassA

class ClassB:
  def method_1():
    a = ClassA()
    a.method2()

then I have ClassA defined as:

b.py

from c import ClassC

class ClassA:
  def method2():
      c = ClassC()
      c.method3()

Now in this code, when writing test for xy.py I want to mock.patch ClassC, is there a way to achieve that in python?

obviously I tried:

mock.patch('a.b.ClassA.ClassC')

and

mock.patch('a.b.c.ClassC')

None of these worked.

Upvotes: 68

Views: 47396

Answers (4)

sezanzeb
sezanzeb

Reputation: 1139

You could patch the __new__ method to return your mock. This should be more robust when refactoring your code, because you won't have to update all your patches when you rename a module.

class_a.py:

from class_b import ClassB

class ClassA:
    def method_1(self):
        b = ClassB()
        b.method_2()

class_b.py:

class ClassB:
    def method_2(self):
        print("original method_2 called")

test.py:

from class_a import ClassA
from class_b import ClassB
from unittest.mock import patch, MagicMock
import unittest

class Test(unittest.TestCase):
    @patch.object(ClassB, '__new__')
    def test(self, b_new_mock):
        a = ClassA()

        b_mock_instance = MagicMock()
        b_mock_instance.method_2.side_effect = lambda *_: print("method_2 patch called")
        b_new_mock.return_value = b_mock_instance

        a.method_1()

if __name__ == '__main__':
    unittest.main()

output:

method_2 patch called
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

Apart from that, you can also use

    @patch.object(ClassB, 'method_2')
    def test(self, method_2_mock):
        method_2_mock.side_effect = lambda *_: print("method_2 patch called")

to only patch method_2 for all objects of ClassB. This also doesn't care where your class_b is located, just the import needs to be right, like with every other python code as well. If this is sufficient, I think you should definitely go for this instead.


As a side note: If your software architecture follows the dependency injection pattern, you can easily replace ClassB with a mock without ever having to use patch.

Upvotes: 0

sirfz
sirfz

Reputation: 4277

Where to patch:

patch() works by (temporarily) changing the object that a name points to with another one. There can be many names pointing to any individual object, so for patching to work you must ensure that you patch the name used by the system under test.

The basic principle is that you patch where an object is looked up, which is not necessarily the same place as where it is defined.

In your case, the lookup location is a.b.ClassC since you want to patch ClassC used in ClassA.

import mock

with mock.patch('a.b.ClassC') as class_c:
    instance = class_c.return_value  # instance returned by ClassC()
    b = ClassB()
    b.method1()
    assert instance.method3.called == True
    

Upvotes: 26

Martijn Pieters
Martijn Pieters

Reputation: 1125058

Each time the method ClassA().method2() is called, the method looks up ClassC as a global, thus finding ClassC in the a.b module. You need to patch that location:

mock.patch('a.b.ClassC')

See the Where to patch section section.

Upvotes: 4

Simeon Visser
Simeon Visser

Reputation: 122526

You need to patch where ClassC is located so that's ClassC in b:

mock.patch('b.ClassC')

Or, in other words, ClassC is imported into module b and so that's the scope in which ClassC needs to be patched.

Upvotes: 57

Related Questions