如何模拟预期 Response 对象的 pydantic BaseModel?

3
我正在为我的API客户端编写测试。我需要模拟get函数,以便它不会发出任何请求。因此,我想返回一个MagicMock对象,而不是返回Response对象。但是,当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


任何想法如何修补/模拟它?(保留HTML,不解释)

1个回答

6

不使用 MagicMock/Mock,你可以为测试创建一个 Response 的子类,然后将 requests.get 覆盖为返回该子类的实例。

这样做可以让你:

  • 将模拟的类型维持为 Response(使 pydantic 愉快)
  • 控制大部分预期的响应行为以进行测试
  • 避免在应用代码中添加测试代码(是的,“一种解决方案是添加带有 MagicMockUnion”,绝对不是正确的方式。)

(我假设 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)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接