読み取り専用プロパティをモックでどのようにモックしますか?
私は試した:
setattr(obj.__class__, 'property_to_be_mocked', mock.Mock())
しかし、問題は、それがクラスのすべてのインスタンスに適用されることです...これは私のテストを破ります。
他に何かアイデアはありますか?オブジェクト全体をモックしたくはありません。この特定のプロパティだけをモックします。
回答:
メソッドを直接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()
実際、答えは(いつものように)ドキュメントにありました。彼らの例に従ったときに、クラスではなくインスタンスにパッチを適用していたというだけです。
これを行う方法は次のとおりです。
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
mock.PropertyMock
それを行う方法です!
PropertyMock
です。執筆時点では存在していませんでした。
プロパティをオーバーライドするオブジェクトがモックオブジェクトである場合は、を使用する必要はありません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
おそらくスタイルの問題ですが、テストでデコレータを好む場合は、@ 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()
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()
@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
@property
。この回答は、他の回答(および他の多くの質問に対する他の回答)が機能しなかったときに私にとっては機能しました。