如何Mock字典子类的__setitem__?单元测试无法验证调用
问题根源
当你执行t["a"] = 1这种下标赋值操作时,Python的底层逻辑是直接调用type(t).__setitem__(t, "a", 1)——也就是从类层面查找__setitem__方法,完全不会去读取实例上的__setitem__属性。这就是为什么你在实例上patch或赋值的Mock对象根本没被触发,断言自然失败。
解决方案
方案1:Patch类的__setitem__方法
既然Python是从类里找特殊方法,那直接针对类进行patch即可:
from unittest import mock from unittest.mock import MagicMock class MyDict(dict): def __setitem__(self, key, value): super().__setitem__(key, value) class TestAssumptions: def test_setitem_called(self): # 直接patch MyDict类的__setitem__方法 with mock.patch.object(MyDict, "__setitem__", new=MagicMock(wraps=MyDict.__setitem__)) as setitem: t = MyDict() t["a"] = 1 # 注意断言时要传入实例、key、value三个参数 setitem.assert_called_once_with(t, "a", 1)
方案2:用计数器替代Mock(适合对比子类行为)
如果你的核心需求是验证__setitem__是否被调用、调用次数,完全可以不用Mock,直接在子类里加个计数器:
from collections import UserDict class MyDict(dict): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setitem_call_count = 0 def __setitem__(self, key, value): self.setitem_call_count += 1 super().__setitem__(key, value) class MyUserDict(UserDict): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setitem_call_count = 0 def __setitem__(self, key, value): self.setitem_call_count += 1 super().__setitem__(key, value) class TestAssumptions: def test_dict_subclass(self): t = MyDict() t["a"] = 1 t["b"] = 2 assert t.setitem_call_count == 2 def test_userdict_subclass(self): t = MyUserDict() t["x"] = 10 assert t.setitem_call_count == 1
这种方式更直观,也方便你对比dict和UserDict子类的行为差异。
方案3:使用纯Python实现的字典包装类
collections.UserDict是纯Python写的字典包装类,它的特殊方法逻辑和内置dict不同——它的__setitem__是通过组合内部的self.data字典实现的,没有内置类型的底层优化限制。不过即使如此,Python调用特殊方法还是会走类层面,所以如果用Mock的话,依然需要patch类的__setitem__,但它的行为更贴近普通Python类,调试起来更方便。
内容的提问来源于stack exchange,提问作者Guillaume
相关产品推荐
相关产品推荐

