인프런 커뮤니티 질문&답변

yuki님의 프로필 이미지
yuki

작성한 질문수

실전! FastAPI 입문

(실습) 테스트 코드 - DELETE API

AssertionError: Expected 'undone' to be called once. Called 0 times.

해결된 질문

작성

·

486

0

def test_update_todo(client, mocker):
    '''
    특정 객체 update 테스트
    # 200
    '''
    mocker.patch(
        "main.get_todo_by_todo_id",
        return_value= ToDo(id=1, contents="todo", is_done=True),
    )
    undone = mocker.patch.object(ToDo, "undone")
    mocker.patch(
        "main.update_todo",
        return_value=ToDo(id=1, contents="todo", is_done=False),
    )

    # API 호출 여부
    response = client.patch("/todos/1", json={"is_done": False})

    undone.assert_called_once_with()

    assert response.status_code == 200  # 성공
    assert response.json() == {"id": 1, "contents": "todo", "is_done": False} # 성공

    # Resetting the mock before the next scenario
    # 404
    mocker.patch(
        "main.get_todo_by_todo_id",
        return_value=None,
    )

    response = client.patch("/todos/1", json={"is_done": True})

    assert response.status_code == 404
    assert response.json() == {"detail": "ToDo Not Found"}


pytest 시 아래와 같은 에러가 발생합니다. 강사님 코드와 상이한 부분도 없고 로직도 맞게 작성한 것 같은데 undone이 한번도 호출이 되지 않았다고 합니다.

During handling of the above exception, another exception occurred:

client = <starlette.testclient.TestClient object at 0xffff89948640>, mocker = <pytest_mock.plugin.MockerFixture object at 0xffff89004e50>

    def test_update_todo(client, mocker):
        '''
        특정 객체 update 테스트
        # 200
        '''
        mocker.patch(
            "main.get_todo_by_todo_id",
            return_value= ToDo(id=1, contents="todo", is_done=True),
        )
        undone = mocker.patch.object(ToDo, "undone")
>       print(undone.assert_called_once_with())
E       AssertionError: Expected 'undone' to be called once. Called 0 times.

tests/test_main.py:106: AssertionError
========================================================== short test summary info ==========================================================
FAILED tests/test_main.py::test_update_todo - AssertionError: Expected 'undone' to be called once. Called 0 times.
======================================================== 1 failed, 5 passed in 0.18s ========================================================
# 

답변 1

0

신동현님의 프로필 이미지
신동현
지식공유자

안녕하세요! 테스트 코드만 봐서는 정확한 원인 파악이 힘들 것 같은데 혹시 handler 코드도 첨부해주실 수 있으실까요?

yuki님의 프로필 이미지
yuki
질문자

from fastapi import FastAPI, Body, HTTPException, Depends
from typing import Optional, List

from database.connection import get_db
from database.repository import get_todos, get_todo_by_todo_id, create_todo, update_todo, delete_todo
from database.orm import ToDo

from sqlalchemy.orm import Session

from schema.response import ToDoListSchema, ToDoSchema
from schema.request import CreateToDoRequest

app = FastAPI()

@app.get("/")
def health_check_handler():
    return {"ping": "pong"}


todo_data = {
    1: {
        "id": 1,
        "contents": "실전! FastAPI 섹션 0 수강",
        "is_done": True
    },
    2: {
        "id": 2,
        "contents": "실전! FastAPI 섹션 1 수강",
        "is_done": False
    },
    3: {
        "id": 3,
        "contents": "실전! FastAPI 섹션 2 수강",
        "is_done": False
    },
}


@app.get("/todos", status_code=200)
# def get_todos_handler(order: str | None = None):
def get_todos_handler(
        order: Optional[str] = None,
        session: Session = Depends(get_db),
):
    todos: List[ToDo] = get_todos(session=session)
    # ret = list(todo_data.values())
    if order and order == "DESC":
        return ToDoListSchema(
            todos=[
                ToDoSchema.from_orm(todo) for todo in todos[::-1]
            ]
        )
    return ToDoListSchema(
        todos=[
            ToDoSchema.from_orm(todo) for todo in todos
        ]
    )


@app.get("/todos/{todo_id}", status_code=200)
def get_todos_handler(
        todo_id: int,
        session: Session = Depends(get_db),
):
    todo: Optional[ToDo] = get_todo_by_todo_id(session=session, todo_id=todo_id)

    # todo = todo_data.get(todo_id)
    if todo:
        return ToDoSchema.from_orm(todo)

    raise HTTPException(status_code=404, detail="ToDo Not Found")


@app.post("/todos", status_code=201)
def create_todo_handler(
        request: CreateToDoRequest,
        session: Session = Depends(get_db),
) -> ToDoSchema:
    '''
    pydantic -> orm -> database
    '''
    todo: ToDo = ToDo.create(request=request)  # id=None

    todo: ToDo = create_todo(
        session=session,
        todo=todo,
    )  # id=int

    return ToDoSchema.from_orm(todo)

    # todo_data[request.id] = request.dict()
    # return todo_data[request.id]


@app.patch("/todos/{todo_id}", status_code=200)
def update_todo_handler(
        todo_id: int,
        # ... : required, embeded : key 값
        is_done: bool = Body(..., embeded=True, example=True),
        session: Session = Depends(get_db),
):
    todo: Optional[ToDo] = get_todo_by_todo_id(session=session, todo_id=todo_id)
    # todo: ToDo | None = get_todo_by_todo_id(session=session, todo_id=todo_id)

    # todo = todo_data.get(todo_id)
    if todo:
        todo.done() if is_done else todo.undone()
        todo: ToDo = update_todo(session=session, todo=todo)
        return ToDoSchema.from_orm(todo)

    raise HTTPException(status_code=404, detail="ToDo Not Found")


@app.delete("/todos/{todo_id}", status_code=204)
def delete_todo_handler(
        todo_id: int,
        session: Session = Depends(get_db),
):
    todo: Optional[ToDo] = get_todo_by_todo_id(session=session, todo_id=todo_id)

    if not todo:
        raise HTTPException(status_code=404, detail="ToDo Not Found")

    # delete
    delete_todo(session=session, todo_id=todo_id)

핸들러 함수입니다!

신동현님의 프로필 이미지
신동현
지식공유자

보통 mocking을 이용한 테스트에서 자주 발생하는 실수는 mocking 하는 메소드 이름에 오타가 있는 경우가 있습니다. 예를 들어, abc 메소드를 mocking 해야 되는데 ab로 mocking 하는 경우입니다. 다만 첨부해주신 코드상 오타가 있어 보이진 않는데요.

이런 경우에는 코드를 한 줄씩 보면서 개별 코드가 잘 동작하는지 디버깅을 해봐야 할 것 같습니다.

먼저 handler 코드의 if todo 분기문이 잘 동작하는지 그래서 분기문 아래의 코드가 잘 실행되는지 확인해야 할 것 같습니다.

현재 코드에서 if todo 분기문이 잘 동작한다면 응답의 상태 코드가 200을 리턴해야 합니다. 테스트 코드에서 undone.assert_called_once_with() 부분을 주석 처리하고 테스트 코드를 동작했을 때 어떻게 결과가 출력되는지 확인 부탁드립니다.

 

yuki님의 프로필 이미지
yuki

작성한 질문수

질문하기