AttributeError: <module 'myapp.services' from '/app/services.py'> 没有属性 'requests'
AttributeError: <module 'myapp.services' from '/app/services.py'> does not have the attribute 'requests'
ID: python/unittest-mock-patch-target-not-found
版本兼容性
| 版本 | 状态 | 引入 | 弃用 | 备注 |
|---|---|---|---|---|
| 3.8 | active | — | — | — |
| 3.11 | active | — | — | — |
| 3.12 | active | — | — | — |
根因分析
unittest.mock.patch 针对的是名称被查找的位置,而非定义位置。若 services.py 使用 `import requests` 并调用 `requests.get`,应 patch 'myapp.services.requests.get';若使用 `from requests import get`,则应 patch 'myapp.services.get'。
English
unittest.mock.patch targets the name where it is looked up, not where it is defined. If services.py does `import requests` and calls `requests.get`, patch 'myapp.services.requests.get'. If it does `from requests import get`, patch 'myapp.services.get'.
解决方案
-
95% 成功率
Identify the binding style in the SUT and patch accordingly: # if SUT: import requests @patch('myapp.services.requests') def test_get(mock_req): ... # if SUT: from requests import get @patch('myapp.services.get') def test_get(mock_get): ... -
90% 成功率
Use pytest-mock's mocker fixture which resolves the same lookup semantics: def test_get(mocker): mocker.patch('myapp.services.requests.get')
无效尝试
常见但无效的做法:
-
75% 失败
Patches the global requests module, not the reference the SUT uses; the real network call still fires.
-
80% 失败
Creates a separate binding; the SUT's binding remains untouched.