I have two questions regarding the mocking of a property with unittest.mock.PropertyMock (code will follow below):
test_prop
output "text_0, text_0" and not "text_0, text_1" as the test test_func
? The output indicates, that the function side_effect_func
in test_prop
gets called only once, while I would have expected it to get called twice, like in test_func
. My use case is that I would like to have a mock that returns a different name (which is a property) depending on how often it was called. This would "simulate" two different instances of Class1 to Class2 in the following minimal example.
The code:
File dut.py :
class Class1():
def __init__(self, name):
self.__name = name
@property
def name(self):
return self.__name
def name_func(self):
return self.__name
class Class2():
def __init__(self, name, class1):
self.__name = name
self.__class1 = class1
@property
def name(self):
return self.__name
@property
def class1(self):
return self.__class1
File test\test_dut.py (the second with-statement produces the exact same behavior when swapped with the first one):
import dut
import unittest
from unittest.mock import patch, PropertyMock
class TestClass2(unittest.TestCase):
def test_func(self):
side_effect_counter = -1
def side_effect_func(_):
nonlocal side_effect_counter
side_effect_counter += 1
return f'text_{side_effect_counter}'
c2_1 = dut.Class2('class2', dut.Class1('class1'))
c2_2 = dut.Class2('class2_2', dut.Class1('class1_2'))
with patch('test_dut.dut.Class1.name_func', side_effect=side_effect_func, autospec=True):
print(f'{c2_2.class1.name_func()}, {c2_1.class1.name_func()}')
def test_prop(self):
side_effect_counter = -1
def side_effect_func():
nonlocal side_effect_counter
side_effect_counter += 1
return f'text_{side_effect_counter}'
c2_1 = dut.Class2('class2', dut.Class1('class1'))
c2_2 = dut.Class2('class2_2', dut.Class1('class1_2'))
with patch.object(dut.Class1, 'name', new_callable=PropertyMock(side_effect=side_effect_func)):
# with patch('test_dut.dut.Class1.name', new_callable=PropertyMock(side_effect=side_effect_func)):
print(f'{c2_2.class1.name}, {c2_1.class1.name}')
Call from command line: pytest -rP test\test_dut.py
This leads to the following output (problematic line marked by me):
============================================================================================== test session starts ==============================================================================================
platform win32 -- Python 3.9.12, pytest-7.1.2, pluggy-1.0.0
rootdir: C:\Users\klosemic\Documents\playground_mocks
plugins: hypothesis-6.46.5, cov-3.0.0, forked-1.4.0, html-3.1.1, metadata-2.0.1, xdist-2.5.0
collected 2 items
test\test_dut.py .. [100%]
==================================================================================================== PASSES =====================================================================================================
_____________________________________________________________________________________________ TestClass2.test_func ______________________________________________________________________________________________
--------------------------------------------------------------------------------------------- Captured stdout call ----------------------------------------------------------------------------------------------
text_0, text_1
_____________________________________________________________________________________________ TestClass2.test_prop ______________________________________________________________________________________________
--------------------------------------------------------------------------------------------- Captured stdout call ----------------------------------------------------------------------------------------------
text_0, text_0 <<<<<< HERE IS THE PROBLEM
=============================================================================================== 2 passed in 0.46s ===============================================================================================
The issue has to do with how you are instantiating the PropertyMock
. To answer your first question about why the second test prints test_0
for both calls, you instantiate the PropertyMock
class during your with patch.object(get_events.Class1, 'name', new_callable=PropertyMock(side_effect=side_effect_func))
call.
Since you instantiate the class, it gets called immediately at the end of that line due to the logic of the __enter__
method in the patch
object. You can see that logic in this line and then this one . Due to that the value of your side_effect
immediately becomes a string, which is essentially the output of the first call to the PropertyMock
. This can be confirmed by changing your function to the following and observing the output:
with patch.object(dut.Class1, 'name', new_callable=PropertyMock(side_effect=side_effect_func)) as mock_prop:
# print(f'{c2_2.class1.name}, {c2_1.class1.name}')
print(mock_prop)
You will notice that this prints text_0
in the console, confirming what has been mentioned above.
To answer your second question, the way to use PropertyMock
in this case would be to change the second test to the following:
with patch.object(dut.Class1, 'name', new_callable=PropertyMock) as mock_prop:
mock_prop.side_effect = side_effect_func
print(f'{c2_2.class1.name}, {c2_1.class1.name}')
Then when you run the tests you get the correct output as shown below.
============================================================= test session starts =============================================================
platform darwin -- Python 3.8.9, pytest-7.0.1, pluggy-1.0.0
rootdir: ***
plugins: asyncio-0.18.3, mock-3.7.0
asyncio: mode=strict
collected 2 items
tests/test_dut.py .. [100%]
=================================================================== PASSES ====================================================================
____________________________________________________________ TestClass2.test_func _____________________________________________________________
------------------------------------------------------------ Captured stdout call -------------------------------------------------------------
text_0, text_1
____________________________________________________________ TestClass2.test_prop _____________________________________________________________
------------------------------------------------------------ Captured stdout call -------------------------------------------------------------
text_0, text_1
============================================================== 2 passed in 0.01s ==============================================================
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.