Python单元测试:如何在MagicMock的side_effect中更新self属性
问题原因
- TestSample1能正常运行的核心:你patch的是
OAuthSession类的真实实例方法,加上autospec=True后,调用方法时会自动把实例对象作为self参数传入side_effect函数;但真实的OAuthSession实例不存在a/g这类属性,必须手动逐层赋值MagicMock,否则访问深层属性会抛AttributeError,配置繁琐。 - TestSample2运行失败的核心:你patch了整个
OAuthSession类,OAuthSession()返回的本身就是MagicMock实例,不是真实类对象,调用mock的方法时不会自动绑定宿主实例作为self传给side_effect,参数匹配逻辑和真实实例方法不一致。 - TestSample3报错的核心:直接创建的MagicMock实例的方法是独立mock对象,调用时不会隐式把外层mock实例作为第一个参数传入side_effect,所以你的
mocked_refresh_access_token收不到预期的self参数,抛出缺参错误。
最优修正方案(完全满足无需逐层手动赋值的需求)
直接修正TestSample3的写法即可,MagicMock本身就支持自动创建任意层级的子属性,不需要手动给每一层赋值Mock,只需要调整side_effect的参数逻辑,用闭包直接引用外层mock实例即可:
import unittest from unittest import mock class OAuthSession(): def refresh_access_token(self): self.access_token = 'refreshed new token' def target_method(auth): if auth.a.b.c.d.e: auth.refresh_access_token() return auth.access_token class TestSampleFixed(unittest.TestCase): def test_target_method(self): oauth_session = mock.MagicMock() # 直接赋值深层属性即可,MagicMock会自动创建中间层级的mock对象,无需手动逐层配置 oauth_session.a.b.c.d.e = True oauth_session.g.h.i.j.k = True # 修正side_effect:mock方法不会自动传self,直接闭包引用外层实例设置属性即可 def _mock_refresh(): oauth_session.access_token = 'mocked refreshed token' oauth_session.refresh_access_token.side_effect = _mock_refresh result = target_method(oauth_session) assert result == 'mocked refreshed token'
这个写法不需要patch任何真实类/方法,不需要逐层手动创建中间属性,代码量比TestSample1少一半以上,运行可以直接通过。
其他可选优化(如果要保留原mocked_refresh_access_token函数)
如果你不想改写原来的接收self的mock函数,可以在调用side_effect时手动把实例传进去,用lambda包一层即可:
def mocked_refresh_access_token(self): self.access_token = 'mocked refreshed token' class TestSampleFixed2(unittest.TestCase): def test_target_method(self): oauth_session = mock.MagicMock() oauth_session.a.b.c.d.e = True oauth_session.g.h.i.j.k = True # 用lambda手动传入self参数 oauth_session.refresh_access_token.side_effect = lambda: mocked_refresh_access_token(oauth_session) result = target_method(oauth_session) assert result == 'mocked refreshed token'
注意:不要尝试给MagicMock的side_effect直接绑定原带self参数的函数,因为mock方法的调用逻辑和Python原生实例方法的绑定逻辑完全不同,原生实例方法会自动绑定宿主实例作为第一个参数,mock方法不会做这个隐式操作。
内容的提问来源于stack exchange,提问作者Sheile
相关产品推荐
相关产品推荐

