Unable to match on JSON content
Closed this issue · 6 comments
When mocking a request body with match_content
, the value passed in gets converted to bytes. However, when making the actual request, the body gets converted to json, even if I use content
and pass an array of bytes. Therefore, I'm unable to match any requests when using the match_content
parameter.
I would like to be able to send json in the request and set a matcher on the mocker to only pass when a request with that json body is sent.
Request:
async with AsyncClient() as client:
response = await client.post("api.test.com", json={"userId": 1, "data": {"names": ["Test1", "Test2"]}})
Test:
@pytest.mark.asyncio
async def test_send_request(httpx_mock: HTTPXMock):
httpx_mock.add_response(url="api.test.com", method="POST", match_content={"userId": 1, "data": {"names": ["Test1", "Test2"]}})
await send_request()
Test failure message:
httpx.TimeoutException: No response can be found for POST request on /api.test.com with b'{"userId": 1, "data": {"names": ["Test1", "Test2"]}}' body amongst:
Match POST requests on api.test.com with {'userId': 1, 'data': {'names': ['Test1', 'Test2']}} body
Hello @colton-flyhomes
Thanks for raising this, for now the feature only works with bytes as there is no way to know for sure what you mean by a dict (is it a form content or a JSON content). So to fix your issue I would advise to match_content on the bytes representation of the JSON body.
I know it's far from ideal, especially when the order of the fields might change. This would be a nice improvement.
Hi @Colin-b
Thanks for the reply. I tried using bytes, however httpx converts the payload to json even if you pass in bytes using the content
keyword.
You can use python dict as JSON content with httpx, it's not an issue. Just make sure that you provide bytes as the value for match_content
parameter of the httpx_mock. Such as the following:
@pytest.mark.asyncio
async def test_send_request(httpx_mock: HTTPXMock):
httpx_mock.add_response(url="api.test.com", method="POST", match_content=b`{"userId": 1, "data": {"names": ["Test1", "Test2"]}}`)
await send_request()
If you prefer to keep the JSON dict in your test case you can also use json.dumps followed by a encode() as in the following:
import json
def to_bytes(json_content: dict) -> bytes:
return json.dumps(json_content).encode()
@pytest.mark.asyncio
async def test_send_request(httpx_mock: HTTPXMock):
httpx_mock.add_response(url="api.test.com", method="POST", match_content=to_bytes({"userId": 1, "data": {"names": ["Test1", "Test2"]}}))
await send_request()
For me this is actually an issue. There is no way to guarantee the order of keys in a dictionary/json object. So match_content=json.dumps(my_test_data).encode('utf-8')
won't work since python doesn't give any guarantee on the order of keys in a dictionary, so matching the order of the keys in my_test_data
to the dictionary in the request before sending with httpx.post(json=mydata)
is not possible.
A simple solution imo would be to add a match_json
argument, and when present, it checks if the Content-Type
header of the request is application/json
and then it does a json.loads(request.body)
and compares the result with match_json
for equality.
Just to clarify this issue in case someone reads this in the future. The proposal is to check the body as JSON decoded content. The check on the Content-Type header will not be a part of this as there is more than one content-type value that can be used in conjonction with JSON encoded content and we wouldnt want to restrict our feature to only one type of usage.
Matching on headers can already be performed via match_headers.
Release 0.24.0 introducing the match_json
parameter is now available on pypi.