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
Aanyway, just override the definition ofcomplicated_method, noMockneeded.self.compicated_methodwill resolve toFakeA.complicated_method` when called fromFakeA.simple_method.return_valueof the overload, then? If I use an overload thecomplicated_methodis not aMagicMock, anymore.FakeA.complicated_methodcan return anything it wants.A.complicated_methoduses.simple_methodandcomplicated_methodis that I can test both and that I can test howsimple_methodbehaves with different return values ofcomplicated_method. Of course I can implement my overload and provide a helper methoddef set_complicated_method_return_value(retval)anddef complicated_method(self): return self.complicated_method_return_valuebut this would mean I reimplement the same functionality theMagicMockprovides with it'sreturn_valueattribute.