Hoe kan ik een alleen-lezen eigenschap bespotten met mock?

Hoe bespot je een alleen-lezen eigenschap met mock?

Ik heb geprobeerd:

setattr(obj.__class__, 'property_to_be_mocked', mock.Mock())

maar het probleem is dat het dan van toepassing is op alle instanties van de klas… die mijn tests verbreekt.

Heeft u een ander idee? Ik wil niet het volledige object bespotten, alleen deze specifieke eigenschap.


Antwoord 1, autoriteit 100%

Ik denk dat het beter is om de eigenschap te bespotten als PropertyMock, in plaats van rechtstreeks de __get__-methode te gebruiken.

Het staat in de documentatie, zoek naar unittest.mock.PropertyMock:
Een mock bedoeld om te worden gebruikt als een eigenschap, of andere descriptor, op een klasse. PropertyMockbiedt de methoden __get__en __set__zodat u een retourwaarde kunt opgeven wanneer deze wordt opgehaald.

Dit is hoe:

class MyClass:
    @property
    def last_transaction(self):
        # an expensive and complicated DB query here
        pass
def test(unittest.TestCase):
    with mock.patch('MyClass.last_transaction', new_callable=PropertyMock) as mock_last_transaction:
        mock_last_transaction.return_value = Transaction()
        myclass = MyClass()
        print myclass.last_transaction
        mock_last_transaction.assert_called_once_with()

Antwoord 2, autoriteit 21%

Eigenlijk stond het antwoord (zoals gewoonlijk) in de documentatie , het is gewoon dat ik de patch op de instantie aan het toepassen was in plaats van op de klasse toen ik hun voorbeeld volgde.

Hier is hoe het te doen:

class MyClass:
    @property
    def last_transaction(self):
        # an expensive and complicated DB query here
        pass

In de testsuite:

def test():
    # Make sure you patch on MyClass, not on a MyClass instance, otherwise
    # you'll get an AttributeError, because mock is using settattr and
    # last_transaction is a readonly property so there's no setter.
    with mock.patch(MyClass, 'last_transaction') as mock_last_transaction:
        mock_last_transaction.__get__ = mock.Mock(return_value=Transaction())
        myclass = MyClass()
        print myclass.last_transaction

Antwoord 3, autoriteit 6%

Als het object waarvan u de eigenschap wilt overschrijven een nepobject is, hoeft u patchniet te gebruiken.

Kan in plaats daarvan een PropertyMocken overschrijf vervolgens de eigenschap op het typevan de mock. Om bijvoorbeeld de eigenschap mock_rows.pageste overschrijven om (mock_page, mock_page,):

te retourneren

mock_page = mock.create_autospec(reader.ReadRowsPage)
# TODO: set up mock_page.
mock_pages = mock.PropertyMock(return_value=(mock_page, mock_page,))
type(mock_rows).pages = mock_pages

Antwoord 4, autoriteit 5%

Waarschijnlijk een kwestie van stijl, maar als je de voorkeur geeft aan decorateurs in tests, kan het antwoordvan @jamescastlefield worden gewijzigd in iets als dit:

class MyClass:
    @property
    def last_transaction(self):
        # an expensive and complicated DB query here
        pass
class Test(unittest.TestCase):
    @mock.patch('MyClass.last_transaction', new_callable=PropertyMock)
    def test(self, mock_last_transaction):
        mock_last_transaction.return_value = Transaction()
        myclass = MyClass()
        print myclass.last_transaction
        mock_last_transaction.assert_called_once_with()

Antwoord 5, autoriteit 4%

In het geval dat u pytestsamen met pytest-mock, u kunt uw code vereenvoudigen en ook voorkomen dat u de context manager, dwz de with-instructie als volgt:

def test_name(mocker): # mocker is a fixture included in pytest-mock
    mocked_property = mocker.patch(
        'MyClass.property_to_be_mocked',
        new_callable=mocker.PropertyMock,
        return_value='any desired value'
    )
    o = MyClass()
    print(o.property_to_be_mocked) # this will print: any desired value
    mocked_property.assert_called_once_with()

Antwoord 6

Als u uw bespotte @propertywilt laten vertrouwen op de originele __get__, kunt u uw aangepaste MockProperty

maken

class PropertyMock(mock.Mock):
    def __get__(self, obj, obj_type=None):
        return self(obj, obj_type)

Gebruik:

class A:
  @property
  def f(self):
    return 123
original_get = A.f.__get__
def new_get(self, obj_type=None):
  return f'mocked result: {original_get(self, obj_type)}'
with mock.patch('__main__.A.f', new_callable=PropertyMock) as mock_foo:
  mock_foo.side_effect = new_get
  print(A().f)  # mocked result: 123
  print(mock_foo.call_count)  # 1

Antwoord 7

Als je niet wilt testen of de nagemaakte eigenschap al dan niet is gebruikt, kun je deze eenvoudig patchen met de verwachte return_value.

with mock.patch(MyClass, 'last_transaction', Transaction()):
    ...

Antwoord 8

Ik werd doorverwezen naar deze vraag omdat ik de Python-versie in een test wilde bespotten. Ik weet niet zeker of dit heel relevant is voor deze vraag, maar sys.versionis duidelijk alleen-lezen (… hoewel technisch gezien een “attribuut” in plaats van een “eigenschap”, veronderstel ik).

Dus na het doorlezen van deze plek en het proberen van een aantal dom ingewikkelde dingen, realiseerde ik me dat het antwoord de eenvoud zelf was:

with mock.patch('sys.version', version_tried):
    if version_tried == '2.5.2':
        with pytest.raises(SystemExit):
            import core.__main__
        _, err = capsys.readouterr()
        assert 'FATAL' in err and 'too old' in err

… kan iemand helpen.


Antwoord 9

Een andere manier voor eigenschappen van nepobjecten is deze:

import mock
mocked_object = mock.Mock()
mocked_object.some_property = "Property value"
print(mocked_object.some_property)

Other episodes