Làm thế nào để chế nhạo một thuộc tính chỉ đọc với mock?


92

Làm thế nào để bạn chế nhạo một tài sản chỉ đọc bằng chế độ giả ?

Tôi đã thử:

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

nhưng vấn đề là nó sau đó áp dụng cho tất cả các trường hợp của lớp ... điều này phá vỡ các bài kiểm tra của tôi.

Bạn có ý tưởng nào khác không? Tôi không muốn chế nhạo đối tượng đầy đủ, chỉ thuộc tính cụ thể này.

Câu trả lời:


164

Tôi nghĩ rằng cách tốt hơn là chế nhạo thuộc tính PropertyMockthay vì chế nhạo __get__phương thức trực tiếp.

Nó được nêu trong tài liệu , tìm kiếm unittest.mock.PropertyMock: Một mô hình được sử dụng như một thuộc tính, hoặc bộ mô tả khác, trên một lớp. PropertyMockcung cấp __get____set__các phương thức để bạn có thể chỉ định giá trị trả về khi nó được tìm nạp.

Đây là cách:

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

Tôi đã phải chế nhạo một phương thức lớp được trang trí như @property. Câu trả lời này phù hợp với tôi khi câu trả lời khác (và các câu trả lời khác cho nhiều câu hỏi khác) thì không.
AlanSE

2
đây là cách nó nên được thực hiện. Tôi muốn có một cách để di chuyển "chấp nhận" câu trả lời
vitiral

4
Tôi thấy việc bao gồm giá trị trả về trong lệnh gọi trình quản lý ngữ cảnh sẽ gọn gàng hơn một chút: `` với mock.patch ('MyClass.last_transaction', new_callable = PropertyMock, return_value = Transaction ()): ... '' '
wodow

Thật vậy, tôi vừa chuyển câu trả lời được chấp nhận sang câu này.
charlax

1
sử dụng mock.patch.object cũng rất hay vì bạn không phải viết tên lớp dưới dạng chuỗi (không thực sự là vấn đề trong ví dụ) và dễ dàng phát hiện / sửa chữa hơn nếu bạn quyết định đổi tên một gói mà chưa đã cập nhật một bài kiểm tra
Kevin

41

Trên thực tế, câu trả lời là (như thường lệ) trong tài liệu , chỉ là tôi đang áp dụng bản vá cho cá thể thay vì lớp khi tôi làm theo ví dụ của họ.

Đây là cách làm:

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

Trong bộ thử nghiệm:

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
mọi người nên sử dụng ví dụ khác. mock.PropertyMocklà cách để làm điều đó!
vitiral

4
Đúng vậy, tại thời điểm viết bài PropertyMockđã không tồn tại.
charlax

6

Nếu đối tượng có thuộc tính bạn muốn ghi đè là đối tượng giả lập, bạn không cần phải sử dụng patch.

Thay vào đó, có thể tạo một PropertyMockvà sau đó ghi đè thuộc tính trên loại mô hình. Ví dụ: để ghi đè thuộc mock_rows.pagestính để trả về (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, đúng những gì tôi muốn (đối tượng autospec'd với một thuộc tính). Và từ một đồng nghiệp không kém 🙋♂️
Đánh dấu McDonald

6

Có thể là vấn đề về phong cách nhưng trong trường hợp bạn thích trang trí hơn trong các thử nghiệm, câu trả lời của @ jamescastlefield có thể được thay đổi thành một cái gì đó như sau:

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

Trong trường hợp bạn đang sử dụng pytestcùng với pytest-mock, bạn có thể đơn giản hóa mã của mình và cũng tránh sử dụng trình quản lý ngữ cảnh, tức là, withcâu lệnh như sau:

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

Nếu bạn không muốn kiểm tra xem thuộc tính giả mạo có được truy cập hay không, bạn có thể chỉ cần vá nó với mong đợi return_value.

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

0

Nếu bạn cần chế nhạo của mình @propertydựa trên bản gốc __get__, bạn có thể tạo tùy chỉnh của mìnhMockProperty

class PropertyMock(mock.Mock):

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

Sử dụng:

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.