I'm writing tests for my API client. I need to mock the get function so that it won't make any requests. So instead of returning a Response object I want to return a MagicMock. But then pydantic raises ValidationError because it is going to the model.
I have the following pydantic model:
class Meta(BaseModel):
raw: Optional[str]
response: Optional[Response]
class Config:
arbitrary_types_allowed = True
which raises:
> ???
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)
The one solution would be to add Union with MagicMock but I really don't want to change the code for tests. That is not the way.
class Meta(BaseModel):
raw: Optional[str]
response: Optional[Union[Response, MagicMock]]
class Config:
arbitrary_types_allowed = True
Any ideas how to patch/mock it?
Instead of using a MagicMock/Mock, you can create a subclass of Response for tests, then patch requests.get to return an instance of that subclass.
This lets you:
Response (making pydantic happy)Union with MagicMock" is definitely not the way.)(I'm going to assume the Response is from the requests library. If it isn't, then appropriately adjust the attributes and methods to be mocked. The idea is the same.)
# 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))),
]
)
Then, in tests, you just need to instantiate a MockResponse and tell patch to return that:
# 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)
As Gino's answer, I would suggest creating a new class, but subclassing MagicMock and not Response or any class mentioned in your pydantic model. Then override the magic __class__ method:
class MockRequest(MagicMock):
@property
def __class__(self):
return Response
Then patch this object as the return value for requests.get or whatever your code needs.
So:
MagicMock object in your test code, benefiting from its speccing and other abilities.isinstance check.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With