Bagaimana cara mengejek properti hanya-baca dengan tiruan?


92

Bagaimana Anda mengejek properti hanya-baca dengan tiruan ?

Saya mencoba:

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

tapi masalahnya adalah itu kemudian berlaku untuk semua contoh kelas ... yang merusak tes saya.

Apakah Anda punya ide lain? Saya tidak ingin mengejek objek penuh, hanya properti khusus ini.

Jawaban:


167

Saya pikir cara yang lebih baik adalah mengejek properti sebagai PropertyMock, daripada mengejek __get__metode secara langsung.

Ini dinyatakan dalam dokumentasi , cari unittest.mock.PropertyMock: Tiruan yang dimaksudkan untuk digunakan sebagai properti, atau keterangan lain, di kelas. PropertyMockmenyediakan __get__dan __set__metode sehingga Anda bisa menentukan nilai yang dikembalikan saat diambil.

Begini caranya:

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

Saya harus mengejek metode kelas yang didekorasi sebagai @property. Jawaban ini berhasil bagi saya ketika jawaban lain (dan jawaban lain untuk banyak pertanyaan lain) tidak.
AlanSE

3
inilah cara yang seharusnya dilakukan. Saya berharap ada cara untuk memindahkan jawaban yang "diterima"
vitiral

4
Saya menemukan termasuk nilai kembali dalam panggilan manajer konteks menjadi sedikit lebih bersih: `` dengan mock.patch ('MyClass.last_transaction', new_callable = PropertyMock, return_value = Transaction ()): ... `` `
wodow

Memang, saya baru saja memindahkan jawaban yang diterima untuk yang satu ini.
charlax

1
menggunakan mock.patch.object juga bagus karena Anda tidak perlu menulis nama kelas sebagai string (sebenarnya tidak menjadi masalah dalam contoh) dan lebih mudah untuk mendeteksi / memperbaiki jika Anda memutuskan untuk mengganti nama paket dan belum memperbarui tes
Kevin

41

Sebenarnya jawabannya adalah (seperti biasa) di dokumentasi , hanya saja saya menerapkan patch ke instance bukan class ketika saya mengikuti contoh mereka.

Berikut cara melakukannya:

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

Di rangkaian pengujian:

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
orang harus menggunakan contoh lain. mock.PropertyMockadalah cara untuk melakukannya!
vitiral

4
Itu benar, pada saat penulisan PropertyMocktidak ada.
charlax

6

Jika objek yang propertinya ingin Anda timpa adalah objek tiruan, Anda tidak perlu menggunakan patch.

Sebagai gantinya, dapat membuat PropertyMockdan menimpa properti pada tipe tiruan. Misalnya, untuk mengganti mock_rows.pagesproperti yang akan dikembalikan (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
Bam, apa yang saya inginkan (objek autospec dengan properti). Dan dari kolega tidak kurang 🙋‍♂️
Mark McDonald

6

Mungkin masalah gaya, tetapi jika Anda lebih suka dekorator dalam tes, jawaban @jamescastlefield dapat diubah menjadi seperti ini:

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

Jika Anda menggunakan pytestbersama dengan pytest-mock, Anda dapat menyederhanakan kode Anda dan juga menghindari menggunakan palungan konteks, yaitu withpernyataan sebagai berikut:

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

Jika Anda tidak ingin menguji apakah properti tiruan telah diakses atau tidak, Anda cukup menambalnya dengan yang diharapkan return_value.

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

0

Jika Anda membutuhkan ejekan Anda @propertyuntuk mengandalkan yang asli __get__, Anda dapat membuat kebiasaan AndaMockProperty

class PropertyMock(mock.Mock):

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

Pemakaian:

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
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.