我正在为我的 API 客户端编写测试。我需要模拟 get
函数,以便它不会发出任何请求。因此,我不想返回 Response
对象,而是返回 MagicMock
。但随后 pydantic 会引发 ValidationError
因为它要进入模型。
我有以下 pydantic 模型:
class Meta(BaseModel):
raw: Optional[str]
response: Optional[Response]
class Config:
arbitrary_types_allowed = True
这会引发:
> ???
E pydantic.error_wrappers.ValidationError: 1 validation error for OneCallResponse
E meta -> response
E instance of Response expected (type=type_error.arbitrary_type; expected_arbitrary_type=Response)
一种解决方案是添加 Union
和 MagicMock
但我真的不想更改测试代码。事实并非如此。
class Meta(BaseModel):
raw: Optional[str]
response: Optional[Union[Response, MagicMock]]
class Config:
arbitrary_types_allowed = True
有什么想法如何修补/模拟它吗?
最佳答案
您可以创建 Response
的子类进行测试,然后修补 requests.get,而不是使用
返回该子类的实例。MagicMock
/Mock
这可以让你:
- 将模拟类型保持为
Response
(让 pydantic 高兴) - 控制测试的大部分预期响应行为
- 避免测试代码污染应用程序代码(是的,“一个解决方案是使用
MagicMock
添加Union
” 是 绝对不是这样的。)
(我假设 Response
来自 requests 库。如果不是,则适当调整要模拟的属性和方法。想法是一样的。)
# TEST CODE
import json
from requests import Response
from requests.models import CaseInsensitiveDict
class MockResponse(Response):
def __init__(self, mock_response_data: dict, status_code: int) -> None:
super().__init__()
# Mock attributes or methods depending on the use-case.
# Here, mock to make .text, .content, and .json work.
self._content = json.dumps(mock_response_data).encode()
self.encoding = "utf-8"
self.status_code = status_code
self.headers = CaseInsensitiveDict(
[
("content-length", str(len(self._content))),
]
)
然后,在测试中,您只需实例化一个 MockResponse
并告诉 patch
返回它:
# APP CODE
import requests
from pydantic import BaseModel
from typing import Optional
class Meta(BaseModel):
raw: Optional[str]
response: Optional[Response]
class Config:
arbitrary_types_allowed = True
def get_meta(url: str) -> Meta:
resp = requests.get(url)
meta = Meta(raw=resp.json()["status"], response=resp)
return meta
# TEST CODE
from unittest.mock import patch
def test_get_meta():
mocked_response_data = {"status": "OK"}
mocked_response = MockResponse(mocked_response_data, 200)
with patch("requests.get", return_value=mocked_response) as mocked_get:
meta = get_meta("http://test/url")
mocked_get.call_count == 1
assert meta.raw == "OK"
assert meta.response == mocked_response
assert isinstance(meta.response, Response)
关于python - 如何模拟需要 Response 对象的 pydantic BaseModel?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69041639/