简体   繁体   中英

How do I patch a python @classmethod to call my side_effect method?

The following code shows the problem.

I can successfully patch object instance and static methods of this SomeClass

However, I can't seem to be able to patch classmethods.

Help much appreciated!

from contextlib import ExitStack
from unittest.mock import patch


class SomeClass:
    def instance_method(self):
        print("instance_method")

    @staticmethod
    def static_method():
        print("static_method")

    @classmethod
    def class_method(cls):
        print("class_method")

# --- desired patch side effect methods ----
def instance_method(self):
    print("mocked instance_method")

def static_method():
    print("mocked static_method")

def class_method(cls):
    print("mocked class_method")

# --- Test ---
obj = SomeClass()

with ExitStack() as stack:
    stack.enter_context(
        patch.object(
            SomeClass,
            "instance_method",
            side_effect=instance_method,
            autospec=True
        )
    )
    stack.enter_context(
        patch.object(
            SomeClass,
            "static_method",
            side_effect=static_method,
            # autospec=True,
        )
    )
    stack.enter_context(
        patch.object(
            SomeClass,
            "class_method",
            side_effect=class_method,
            # autospec=True
        )
    )


    # These work
    obj.instance_method()
    obj.static_method()

    # This fails with TypeError: class_method() missing 1 required positional argument: 'cls'
    obj.class_method()

General solution

A way to patch a classmethod would be to use new=classmethod(class_method) instead of side_effects=class_method .
This works pretty well in general.

Downside

Using new , the patched object isn't necessarily an instance of Mock , MagicMock , AsyncMock or PropertyMock anymore (During the rest of the answer i'll only reference Mock as all the others are subclasses of it).
It is only then an instance of these when you explicitly specify it to be one via eg new=Mock(...) or ommit the attribute completely.
That wouldn't be the case with the solution provided at the top of this answer. So when you try to eg check if the function already got called using obj.class_method.assert_called() , it'll give an error saying that function has no attribute assert_called which is caused by the fact that the patched object isn't an instance of Mock , but instead a function .

Unfortunately I don't see any solution to this downside in that scenario at the moment

Concluded differences between new and side_effect :

  • new specifies what object to patch the target with (doesn't necessarily have to be an instance of Mock )
  • side_effect specifies the side_effect of the Mock instance that gets created when using patch without new Also they don't play very well together, so only one of these can/should be used in the same patch(...) .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM