Home > Blockchain >  Fast API with pytest using AsyncClient gives 422 on post?
Fast API with pytest using AsyncClient gives 422 on post?

Time:03-17

I'm trying to send a request to an api using pytest through httpx.AsynClient

@pytest.mark.anyio
async def test_device_create_with_data(self, client, random_uuid):
    device_create = DeviceCreateFactory.build(subId=random_uuid)

    json = device_create.json(by_alias=True)

    response = await client.post("/device", json=json)

    assert response.status_code == 200

Client fixture:

from httpx import AsyncClient


@pytest.fixture(scope="session")
async def client():
    async with AsyncClient(
            app=app,
            base_url="http://test/api/pc",
            headers={"Content-Type": "application/json"}
    ) as client:
        yield client

API endpoint:

@device_router.post("/device", response_model=CommonResponse)
async def create_device(device: DeviceCreate):
    _, err = await crud_device.create_device(device)

    if err:
        return get_common_response(400, err)

    return get_common_response(200, "ok")

Schemas:

class DeviceBase(BaseModel):
    device_id: StrictStr = Field(..., alias='deviceId')
    device_name: StrictStr = Field(..., alias='deviceName')
    device_type: StrictStr = Field(..., alias='deviceType')
    age_mode: AgeModeType = Field(..., alias='ageMode')

    class Config:
        allow_population_by_field_name = True
        validate_all = True
        validate_assignment = True


class DeviceCreate(DeviceBase):
    sub_id: StrictStr = Field(..., alias='subId')

    class Config:
        orm_mode = True

Factory:

from pydantic_factories import ModelFactory

from app.core.schemas.device import DeviceCreate


class DeviceCreateFactory(ModelFactory):
    __model__ = DeviceCreate

And i'm getting a 422 error with following response content:

"message":"bad request","details":{"deviceId":"field required","deviceName":"field required","deviceType":"field required","ageMode":"field required","subId":"field required"}

Then i examined the data of the request being sent and got:

b'"{\\"deviceId\\": \\"de\\", \\"deviceName\\": \\"\\", \\"deviceType\\": \\"\\", \\"ageMode\\": \\"child\\", \\"subId\\": \\"11aded61-9966-4be1-a781-387f75346811\\"}"'

Seems like everything is okay, but where is the trouble then?

I've tried to examine the request data in exception handler of 422 I've made:

@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):

    print(await request.json())

    response = validation_error_response(exc)
    return JSONResponse(
        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
        content=jsonable_encoder(response.dict())
    )

But code after print is unreachable, because await request.json() never ends and runs forever trying to print a request json

Is there a way to manage this problem? Thanks for any suggest!

P.S.

python version: 3.8.9

fastapi version: 0.68.1

httpx version: 0.21.1

CodePudding user response:

You're double encoding your content as JSON - you're both asking for it to be returned as a JSON string, and then telling your request method to encode it as JSON a second time. json= as an argument to the method on the client converts the given data to JSON - it does not expect already serialized JSON.

You can see this in your request string because it starts with " and not with { as you'd expect:

b'"{\
  ^

Instead build your model around a dictionary - or as I'd prefer in a test - build the request by hand, so that you're testing how you imagine an actual request should look like.

You can use dict in the same was as you'd use json for a Pydantic model:

device_create = DeviceCreateFactory.build(subId=random_uuid)

response = await client.post("/device", json=device_create.dict(by_alias=True))

assert response.status_code == 200
  • Related