0

I would like to write an unit test for an instance method of a class under test containing some simple logic but calling another more complicated instance method.

I would like to wrap the complicated instance method in a mock. Additionally, it should be possible to forwards calls to the original method via the mock, when requested. If the call is forwarded, the spec of the original method should be used.

I would like to control that via the return value of the mock, if it is unittest.mock.DEFAULT pass the call through, if it is something else, mock out the entire method.

In the code, I came up with, I define a class under test (A) and a derived fake class (FakeA) which mocks out the complicated method. The derived class serves as an isolation layer so that dependencies of the test code to the concrete production code are centralized and kept at a minimum level.

One of the tests checks the logic of the simple_method and mocks out the complicated_method at all. The other test should call the complicated_method via the simple_method but mock out external dependencies.

The problem now is that the spec of the complicated_method is not determined correctly. Therefore I get a failing test1 with the error message:

 if self._mock_wraps is not None: > return self._mock_wraps(*args, **kwargs) E TypeError: A.complicated_method() missing 1 required positional argument: 'self' 

It seems, the mock is errornously considered as a classmethod whereas the original method is an instancemethod.

How can I fix this?

import unittest.mock from unittest.mock import MagicMock class A: def simple_method(self): return self.complicated_method() def complicated_method(self): # Calls some external dependency return 42 class FakeA(A): def __init__(self): super().__init__() type(self).complicated_method = MagicMock(wraps=A.complicated_method, autospec=True, return_value=unittest.mock.DEFAULT) """ Besides mocking out nested calls, the Fake class should provide some additional methods wrapping internals of class A so that the tests need only minimal adaption when the production code changes """ class TestClassA: def test1(self): # This test lets the call pass through to the original method # Therefor we assume here, that external dependencies have been mocked out a = FakeA() assert a.simple_method() == 42 def test2(self): # This test mocks out the original method at all providing a fake return_value a = FakeA() a.complicated_method.return_value = 77 assert a.simple_method() == 77 

Here is my second attempt, which fails for the same reason:

class FakeA(MagicMock(wraps=A, autospec=A)): def __init__(self): super().__init__() type(self).complicated_method.return_value=unittest.mock.DEFAULT 
12
  • If you're going to subclass A anyway, just override the definition of complicated_method, no Mock needed. self.compicated_method will resolve to FakeA.complicated_method` when called from FakeA.simple_method. Commented Jun 20, 2024 at 14:28
  • Yes, but how do I set the return_value of the overload, then? If I use an overload the complicated_method is not a MagicMock, anymore. Commented Jun 20, 2024 at 14:29
  • FakeA.complicated_method can return anything it wants. Commented Jun 20, 2024 at 14:30
  • If you're going to patch anything, patch the external dependency that A.complicated_method uses. Commented Jun 20, 2024 at 14:31
  • The reason I have two methods simple_method and complicated_method is that I can test both and that I can test how simple_method behaves with different return values of complicated_method. Of course I can implement my overload and provide a helper method def set_complicated_method_return_value(retval) and def complicated_method(self): return self.complicated_method_return_value but this would mean I reimplement the same functionality the MagicMock provides with it's return_valueattribute. Commented Jun 20, 2024 at 14:36

0

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.