読み取り専用プロパティをモックでモックする方法は?


92

読み取り専用プロパティをモックでどのようにモックしますか?

私は試した:

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

しかし、問題は、それがクラスのすべてのインスタンスに適用されることです...これは私のテストを破ります。

他に何かアイデアはありますか?オブジェクト全体をモックしたくはありません。この特定のプロパティだけをモックします。

回答:


165

メソッドを直接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 2016年

2
これはそれが行われるべき方法です。「受け入れられた」答えを動かす方法があればいいのにと思います
vitiral 2016

4
コンテキストマネージャーの呼び出しに戻り値を含めると、少しわかりやすくなります: `` `with mock.patch( 'MyClass.last_transaction'、new_callable = PropertyMock、return_value = Transaction()):...` ``
wodow

確かに、私は受け入れられた答えをこれに移しました。
charlax 2018

1
mock.patch.objectを使用することも、クラス名を文字列として記述する必要がなく(例では実際には問題ではありません)、パッケージの名前を変更することにした場合に検出/修正するのが簡単であるため、便利です。テストを更新
Kevin

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それを行う方法です!
vitiral 2016

4
正解PropertyMockです。執筆時点では存在していませんでした。
charlax

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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.