Error testing an endpoint that uploads a text file using FastAPI + pytest

Question:

I have an endpoint in fastapi app that does the following:

  1. Checks if the file is a text file (.txt)
  2. Uploads the file to the S3 bucket

The codes:

upload.py

from app.services.file_upload import file_uploader
from fastapi import APIRouter, File, UploadFile

router = APIRouter(
    prefix="/upload-file",
    tags=["Upload File"],
    responses={
        200: {'description': 'Success'},
        400: {'description': 'Bad Request'},
        422: {'description': 'Unprocessable Entity'},
        500: {'description': 'Internal Server Error'}
    }
)

@router.post('/')
def upload_file(upload_file: UploadFile = File(
        ...,
        description='Upload file'
)):
    filename = upload_file.filename
    file_content = upload_file.file
    return file_uploader(filename, file_content)

The code where the check is done

file_upload.py

import io
from typing import BinaryIO, Dict

import boto3
from app.core.config import settings
from fastapi import HTTPException

def file_uploader(filename: str, file_content: BinaryIO) -> Dict[str, str]:
    if not filename.lower().endswith('.txt'):
        raise HTTPException(status_code=422, detail='Only .txt file allowed')

    s3 = boto3.client('s3')
    file_content = io.BytesIO(file_content.read())
    s3.upload_fileobj(file_content, settings.S3_BUCKET, filename)
    return {'message': 'File uploaded.'}

I want to test this endpoint to see if the file being uploaded is a text file or not.

conftest.py

from typing import Generator

import pytest
from app.main import app
from fastapi.testclient import TestClient


@pytest.fixture(scope="module")
def client() -> Generator:
    with TestClient(app) as c:
        yield c

test_upload.py

import pytest
from fastapi import HTTPException
from fastapi.testclient import TestClient

def test_upload_file(client: TestClient):
    test_file = 'test_file.docx'
    files = {'file': ('test_file.docx', open(test_file, 'rb'))}
    with pytest.raises(HTTPException) as err:
        client.post('/api/v1/upload-file/', files=files)
    assert err.value.status_code == 422

When I run this code, I get the following error

FAILED app/tests/api/v1/test_upload.py::test_upload_file - Failed: DID NOT RAISE <class 'fastapi.exceptions.HTTPException'>

What is the mistake I am doing?

Asked By: Junkrat

||

Answers:

I guess there’s a problem with the file name, in your endpoint upload_file is expected, but in your test, you’re passing an object with file name. So you get 422 Unprocessable Entity exception. You can easily debug it yourself:

response = client.post('/api/v1/upload-file/', files=files)
print(response.json()) # {'detail': [{'loc': ['body', 'upload_file'], 'msg': 'field required', 'type': 'value_error.missing'}]}

So, to fix your problem pass the expected file name:

files = {'upload_file': ('test_file.docx', open(test_file, 'rb'))}
Answered By: funnydman

The HTTPException in the FastAPI app will not propagate out, FastAPI will catch it and use it to return an HTTP error response (otherwise, raising an exception would crash your entire app).

Since what TestClient is doing is essentially just calling your API as any other client would, what you want to do is test that you get the correct error response. Something along the lines of

import pytest
from fastapi.testclient import TestClient

def test_upload_file(client: TestClient):
    test_file = 'test_file.docx'
    files = {'file': ('test_file.docx', open(test_file, 'rb'))}
    response = client.post('/api/v1/upload-file/', files=files)
    assert response.status_code == 422
    assert response.json() == {"detail": "Only .txt file allowed"}

Answered By: M.O.
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.