我正在为我的 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
有什么想法如何修补/模拟它吗?
您可以创建
MagicMock
的子类进行测试,然后修补 Mock
以返回该子类的实例,而不是使用 Response
/requests.get
。
这可以让您:
Response
(让pydantic快乐)Union
和
MagicMock
”是绝对不是这样的方法。)
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)
Gino 的回答,我建议创建一个新类,但子类化 MagicMock
而不是
Response
或 pydantic 模型中提到的任何类。然后重写神奇的
__class__
方法:
class MockRequest(MagicMock):
@property
def __class__(self):
return Response
然后将此对象修补为 requests.get
的返回值或您的代码需要的任何内容。所以:
MagicMock
对象,并受益于其
spec
cing 和其他功能。
isinstance
检查的对象。