Як знущатися над властивістю лише для читання за допомогою mock?


92

Як ви знущаєтесь з властивості лише для читання за допомогою mock ?

Я намагався:

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

але проблема в тому, що це тоді застосовується до всіх екземплярів класу ... що порушує мої тести.

У вас є якась інша ідея? Я не хочу знущатись над повним об’єктом, лише з цією конкретною властивістю.

Відповіді:


167

Я думаю, що кращий спосіб - знущатися над властивістю як PropertyMock, а не знущатися над __get__методом безпосередньо.

Це зазначено в документації , шукайте unittest.mock.PropertyMock: Макет, призначений для використання як властивість або інший дескриптор класу. PropertyMockнадає __get__та __set__методи, щоб ви могли вказати повернене значення при його отриманні.

Ось як:

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()

Мені довелося глузувати над методом класу, оформленим як @property. Ця відповідь спрацювала для мене, коли інша відповідь (та інші відповіді на багато інших питань) не давала.
AlanSE

3
так слід робити. Я хотів би, щоб був спосіб перенести "прийняту" відповідь
віталій

4
Я вважаю, що значення повернення у виклику диспетчера контексту є дещо чистішим: `` з mock.patch ('MyClass.last_transaction', new_callable = PropertyMock, return_value = Transaction ()): ... ``
wodow

Справді, я просто переніс прийняту відповідь на цю.
шарлакс,

1
використання mock.patch.object також приємно, оскільки вам не потрібно писати ім’я класу як рядок (насправді це не проблема в прикладі), і його легше виявити / виправити, якщо ви вирішили перейменувати пакет, а не оновив тест
Кевін

41

Власне, відповідь була (як завжди) у документації , просто я застосовував патч до екземпляра замість класу, коли наслідував їх приклад.

Ось як це зробити:

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

У наборі тестів:

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

14
люди повинні використати інший приклад. mock.PropertyMockце спосіб це зробити!
вітірал

4
Це правильно, на момент написання статті PropertyMockне існувало.
шарлакс

6

Якщо об’єкт, властивість якого ви хочете замінити, є фіктивним об’єктом, вам не потрібно використовувати patch.

Натомість можна створити а, PropertyMockа потім перевизначити властивість за типом макета. Наприклад, щоб перевизначити mock_rows.pagesвластивість для повернення (mock_page, mock_page,):

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

1
Бам, саме те, що я хотів (автоспецифікація об’єкта з властивістю). І від колеги не менше 🙋‍♂️
Марк МакДональд

6

Ймовірно , питання стилю , але в разі , якщо ви віддаєте перевагу декоратор в тестах @ jamescastlefield - х відповідь може бути змінений на що - щось на зразок цього:

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()

6

Якщо ви використовуєте pytestразом із pytest-mock, ви можете спростити свій код, а також уникати використання контекстних ясел, тобто withвисловлювання наступним чином:

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()

0

Якщо ви не хочете перевіряти, чи здійснювався доступ до знущаного майна, ви можете просто виправити його з очікуваним return_value.

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

0

Якщо вам потрібно, щоб ваш глузувач @propertyпокладався на оригінал __get__, ви можете створити свій власнийMockProperty

class PropertyMock(mock.Mock):

    def __get__(self, obj, obj_type=None):
        return self(obj, obj_type)

Використання:

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.