如何模拟期望响应对象的pydantic BaseModel ?



我正在为我的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)

一个解决方案是添加UnionMagicMock,但我真的不想改变测试代码。不是这样的。

class Meta(BaseModel):
raw: Optional[str]
response: Optional[Union[Response, MagicMock]]
class Config:
arbitrary_types_allowed = True

有任何想法如何补丁/模拟它?

不使用MagicMock/Mock,您可以创建Response的子类进行测试,然后修补requests.get以返回该子类的实例。

这允许你:

  • 将mock的类型保持为Response(使pydantic高兴)
  • 控制测试的大部分预期响应行为
  • 避免用测试代码污染应用程序代码(是的,一个解决方案是在MagicMock中添加Union">绝对不是这样的

(我将假设Response是来自请求)图书馆。如果不是,那么适当地调整要模拟的属性和方法。)

# 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)

最新更新