You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用PropertyMock的side_effect时能否访问self对象?

问题:Mock类的Property属性时无法访问self对象

尝试Mock类的property属性,希望根据对象其他状态控制返回值,编写测试代码后运行报错:TypeError: ... missing 1 required positional argument: 'self'

示例代码

import datetime
from pytest_mock import MockFixture


class MyObject:
    def __init__(self, name: str):
        self._name = name
        self._creation_time = datetime.datetime.now()

    @property
    def creation_time(self) -> datetime.datetime:
        # 一系列计算逻辑
        # return_value = ... 
        return return_value
        

def test_ordered_creation_time(mocker: MockFixture) -> None:

    def creation_time_side_effect(self) -> datetime.datetime:
        if self._name == 'first_created':
            return datetime.datetime(2020, 1, 1)
        elif self._name == 'second_created':
            return datetime.datetime(2020, 2, 1)

    mock_creation_time = mocker.patch.object(
        MyObject,
        'creation_time',
        new_callable=mocker.PropertyMock)
    mock_creation_time.side_effect = creation_time_side_effect

    first_created = MyObject('first_created')
    second_created = MyObject('second_created')

    assert first_created.creation_time < second_created.creation_time

核心报错

E  TypeError: test_ordered_creation_time.<locals>.creation_time_side_effect() missing 1 required positional argument: 'self'

完整报错栈:

====================================================================================================================================================================================== FAILURES ======================================================================================================================================================================================
_____________________________________________________________________________________________________________________________________________________________________________ test_ordered_creation_time _____________________________________________________________________________________________________________________________________________________________________________

mocker = <pytest_mock.plugin.MockerFixture object at 0x103fb1210>

    def test_ordered_creation_time(mocker: MockFixture) -> None:

        def creation_time_side_effect(self) -> datetime.datetime:
            if self._name == 'first_created':
                return datetime.datetime(2020, 1, 1)
            elif self._name == 'second_created':
                return datetime.datetime(2020, 2, 1)

        mock_creation_time = mocker.patch.object(
            MyObject,
            'creation_time',
            new_callable=mocker.PropertyMock)
        mock_creation_time.side_effect = creation_time_side_effect

        first_created = MyObject('first_created')
        second_created = MyObject('second_created')

>       assert first_created.creation_time < second_created.creation_time

dummy.py:38:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/homebrew/Cellar/python@3.11/3.11.4/Frameworks/Python.framework/Versions/3.11/lib/python3.11/unittest/mock.py:2946: in __get__
    return self()
/opt/homebrew/Cellar/python@3.11/3.11.4/Frameworks/Python.framework/Versions/3.11/lib/python3.11/unittest/mock.py:1124: in __call__
    return self._mock_call(*args, **kwargs)
/opt/homebrew/Cellar/python@3.11/3.11.4/Frameworks/Python.framework/Versions/3.11/lib/python3.11/unittest/mock.py:1128: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <PropertyMock name='creation_time' id='4361753680'>, args = (), kwargs = {}, effect = <function test_ordered_creation_time.<locals>.creation_time_side_effect at 0x103f9e3e0>

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method

        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
                    raise result
            else:
>               result = effect(*args, **kwargs)
E               TypeError: test_ordered_creation_time.<locals>.creation_time_side_effect() missing 1 required positional argument: 'self'

/opt/homebrew/Cellar/python@3.11/3.11.4/Frameworks/Python.framework/Versions/3.11/lib/python3.11/unittest/mock.py:1189: TypeError

原因分析

是的,PropertyMock的side_effect确实无法直接接收self参数,原因如下:

  • PropertyMock模拟的是属性的getter方法,但它的side_effect机制会把传入的函数当作普通函数调用,不会自动绑定到实例对象上。
  • 从报错栈可见,调用副作用函数时args是空元组,没有传入实例对象,导致自定义的副作用函数因缺少self参数报错。

解决方法

方法1:自定义PropertyMock子类,传递实例对象

重写__get__方法,在调用side_effect时主动传入实例对象:

import datetime
from pytest_mock import MockFixture
from unittest.mock import PropertyMock


class InstanceBoundPropertyMock(PropertyMock):
    def __get__(self, instance, owner):
        if self.side_effect is not None:
            return self.side_effect(instance)
        return super().__get__(instance, owner)


class MyObject:
    def __init__(self, name: str):
        self._name = name
        self._creation_time = datetime.datetime.now()

    @property
    def creation_time(self) -> datetime.datetime:
        return self._creation_time
        

def test_ordered_creation_time(mocker: MockFixture) -> None:

    def creation_time_side_effect(instance) -> datetime.datetime:
        if instance._name == 'first_created':
            return datetime.datetime(2020, 1, 1)
        elif instance._name == 'second_created':
            return datetime.datetime(2020, 2, 1)

    mock_creation_time = mocker.patch.object(
        MyObject,
        'creation_time',
        new_callable=InstanceBoundPropertyMock)
    mock_creation_time.side_effect = creation_time_side_effect

    first_created = MyObject('first_created')
    second_created = MyObject('second_created')

    assert first_created.creation_time < second_created.creation_time

方法2:针对单个实例单独Mock属性

如果不需要全局Mock类的属性,可直接为每个实例单独设置Mock值:

def test_ordered_creation_time(mocker: MockFixture) -> None:
    first_created = MyObject('first_created')
    second_created = MyObject('second_created')

    # 分别替换每个实例的creation_time属性
    mocker.patch.object(first_created, 'creation_time', new_callable=PropertyMock, return_value=datetime.datetime(2020,1,1))
    mocker.patch.object(second_created, 'creation_time', new_callable=PropertyMock, return_value=datetime.datetime(2020,2,1))

    assert first_created.creation_time < second_created.creation_time

方法3:钩子原生属性的__get__方法

利用属性描述符的__get__机制,直接替换原生getter逻辑:

def test_ordered_creation_time(mocker: MockFixture) -> None:
    original_get = MyObject.creation_time.__get__

    def mock_get(instance, owner):
        if instance._name == 'first_created':
            return datetime.datetime(2020,1,1)
        elif instance._name == 'second_created':
            return datetime.datetime(2020,2,1)
        return original_get(instance, owner)

    mocker.patch.object(MyObject.creation_time, '__get__', mock_get)

    first_created = MyObject('first_created')
    second_created = MyObject('second_created')

    assert first_created.creation_time < second_created.creation_time

内容的提问来源于stack exchange,提问作者Hakan Baba

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 00:45:39