python - 如何模拟需要 Response 对象的 pydantic BaseModel?

标签 python python-3.x unit-testing mocking pydantic

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

有什么想法如何修补/模拟它吗?

最佳答案

您可以创建 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/

相关文章:

unit-testing - 使用easymock在spring mvc中进行服务层测试

python - 如何对过滤器进行单元测试?

python - 加速 Python 2.7 中的函数

python - 如何从文件夹中读取多个numpy数组

python - 打破两个for循环

python - 在 Python 3 中写入文件时,TypeError : a bytes-like object is required, 不是 'str'

python - 如何获取 List 元素的引用?

python - 从网站获取货币并以字典形式返回

python - 在方法的开头和结尾做一些事情

python-3.x - 使用 pytest 对包含某个类的实例的某个函数进行单元测试