1

My code has the following structure:

I have a class MyClass that inherits from BaseClass (this is an incidental point and not the source of my problem). Then I have another class MyClassManager that calls the methods of MyClass.

I am writing a unittest for a method of MyClassManager and I want to control the return value of one of the methods of MyClass while autospeccing the rest.

In my test I have created a Mock for MyClass by patching the class with autospec=True. Then I have tried to patch the method MyClass.method_to_patch and replace it with Substitute.substitute_method. So far, so good.

But now when I run the test, the class manager creates an instance of MyClass that is a fully autospecced Mock, but it doesn't patch the method I want to substitute.

Is there a way to combine these two patch decorators to achieve what I want?

class Substitute:

    def substitute_method(self, arg1, arg2):
        print("Running substitute method")
        return (arg1 > 0 and arg2 > 0)


class BaseClass:

    def method_to_patch(self, arg1, arg2):
        return arg1 == arg2


class MyClass(BaseClass):

    def myclass_method(self):
        print("myclass method called")


class MyClassManager:

    def method_to_test(self):
        my_class = MyClass()
        my_class.myclass_method()
        my_class.method_to_patch(10, 100)


class TestMyClass(unittest.TestCase):

    @patch.object(MyClass, "method_to_patch", Substitute.substitute_method)
    @patch("__main__.MyClass", autospec=True)
    def test_method_to_test(self, mock_class):
        class_manager = MyClassManager()
        class_manager.method_to_test()
        print(mock_class.call_count)


if __name__ == "__main__":
    unittest.main()
berkelem
  • 2,005
  • 3
  • 18
  • 36

2 Answers2

1

I found a clue to the answer at the following page where it talks about mocking nested attribute calls: https://www.integralist.co.uk/posts/mocking-in-python/. The same logic applies to method calls.

It is not enough to manually adjust the mocked object - you have to adjust the return_value of the mocked object.

So here is the way the test should look:

class TestMyClass(unittest.TestCase):

    @patch("__main__.MyClass", autospec=True)
    def test_method_to_test(self, mock_class):
        mock_class.return_value.method_to_patch = Substitute.substitute_method
        class_manager = MyClassManager()
        class_manager.method_to_test()
        print(mock_class.call_count)

Now I have a mock object in place of MyClass so MyClass.myclass_method is also mocked, but I can substitute Substitute.substitute_method in place of MyClass.method_to_patch as I wanted.

One final note - the substitute_method is actually a staticmethod so it should look like this:

class Substitute:

    @staticmethod
    def substitute_method(arg1, arg2):
        print("Running substitute method")
        return (arg1 > 0 and arg2 > 0)
berkelem
  • 2,005
  • 3
  • 18
  • 36
-1

To mock a method in a class to return a specific value use @patch. object. To mock a method in a class with @patch. object but return a different value each time it is called, use side_effect.

  • I have tried `side_effect=Substitute.substitute_method` but that's not the problem. I'm looking for a way to mock a class and patch one of its methods without instantiating it directly. – berkelem Mar 04 '20 at 16:25