0

I have two classes: class One that does some stuff and class Wrapper that translates the API of One. To test the Wrapper I want to mock the class One. My problem is that One sets a number of attributes in it's __init__ method and I'd like my test to throw an error when these attributes are changed (e.g. renamed or deleted) in One but not in Wrapper.

I have tried to initialize an instance of One and to use it as a spec, but the resulting mock is non-callable:

from unittest import mock
import pytest

class One:
    def __init__(self, a):
        self.a = a

spec_obj = One(a='foo')

# working code, the test should pass
class Wrapper:
    def __init__(self, wa):
        self._one = One(a=wa)
        self.wa = self._one.a

@mock.patch(__name__ + '.One', autospec=spec_obj)
def test_wrapper(MockOne):
    MockOne.return_value.a = test_a = 'bar'
    
    wrapper_obj = Wrapper(wa=test_a)

    MockOne.assert_called_once_with(a=test_a)
    assert wrapper_obj.wa == test_a

which throws the error:

TypeError: 'NonCallableMagicMock' object is not callable

since the spec spec_obj is non-callable.

If I set autospec=True, everything works but the test passes even when the One's attribute is renamed.

vlc146543
  • 1
  • 2

1 Answers1

0

MockOne.return_value produces a Mock that you configure, but when the mock is actually called, you get a different Mock that hasn't been properly configured. You need to configure MockOne.return_value directly.

@mock.patch('One', autospec=True)
def test_wrapper(MockOne):
    MockOne.return_value.a = test_a = 'bar'
    
    wrapper_obj = Wrapper(wa=test_a)

    MockOne.assert_called_once_with(a=test_a)
    assert wrapper_obj.wa == test_a
chepner
  • 497,756
  • 71
  • 530
  • 681
  • I do not quite understand your answer. You have combined two lines in my example code in a single one, which can indeed be done, but this doesn't change the overall behaviour, the mock is still non-callable. – vlc146543 Dec 22 '22 at 14:15
  • What happens if you get rid of `autospec=True`? It's not clear from the (simplified?) example why it is necessary. You don't need the real `One.__init__` to set the attribute value, because that's what `MockOne.return_value.a = 'bar'` does. – chepner Dec 22 '22 at 14:22
  • Yes, my code example is oversimplified. The actual classes have more methods called in Wrapper with more attributes of class One. The `autospec` makes sure that my mock indeed resembles the real object. Anyway, I've got the answer, see my comment to the original question. – vlc146543 Dec 22 '22 at 20:25