Search code examples
pythonmockingcallable-object

Mocking the __call__ method of a class with unittest.mock.patch


I have a class (ClassToBeMocked) that is instantiated in the init of another class (ContainerClass). I would like to mock the instantiated ClassToBeMocked and replace its dunder call method.

My code works for regular methods but not for the call dunder method.

from unittest.mock import patch, Mock, MagicMock

class ClassToBeMocked:
    def __call__(self, x):
            return 42

    def regular_method(self, x):
            return 42

class ContainerClass:
    def __init__(self):
        self.runner = ClassToBeMocked()

    def run_a(self):
        return self.runner(x="x")
        
    def run_b(self):
        return self.runner.regular_method(x="x")
    
    
with patch("__main__.ClassToBeMocked") as mock:
    instance = mock.return_value
    instance.__call__ = MagicMock(return_value="Hey")
    instance.regular_method = MagicMock(return_value="Hey")
    
    test = ContainerClass()
    print(test.run_a())
    print(test.run_b())

This outputs :

<MagicMock name='ClassToBeMocked()()' id='140033866116784'>
Hey

While I want to have

Hey
Hey

Solution

  • In the example below, instance_cls() does not call instance_cls.__call__. It calls type(instance_cls).__call__(instance_cls) as python actually calls the __call__ method defined on the class, not the instance (for more information check this answer on stack overflow).

    >>> class ClassToBeMocked:
    ...     def __call__(self):
    ...         return 42
    ...
    >>> instance_cls = ClassToBeMocked()
    >>> instance_cls() 
    ... 42
    

    Regarding you example, just change instance.__call__ = MagicMock(return_value="Hey")to instance.__class__.__call__ = MagicMock(return_value="Hey") and it should work.

    Full code below :

    from unittest.mock import patch, Mock, MagicMock
    
    class ClassToBeMocked:
        def __call__(self, x):
                return 42
    
        def regular_method(self, x):
                return 42
    
    class ContainerClass:
        def __init__(self):
            self.runner = ClassToBeMocked()
    
        def run_a(self):
            return self.runner(x="x")
            
        def run_b(self):
            return self.runner.regular_method(x="x")
        
        
    with patch("__main__.ClassToBeMocked") as mock:
        instance = mock.return_value
        instance.__class__.__call__ = MagicMock(return_value="Hey")
        instance.regular_method = MagicMock(return_value="Hey")
        
        test = ContainerClass()
        print(test.run_a())  # print "Hey"
        print(test.run_b())  # print "Hey"