# request.py
from pydantic import BaseModel
class CreateToDoRequest(BaseModel):
contents: str
is_done: bool
# main.py
@app.post("/todos", status_code=201)
# FastAPI가 알아서 reqeustBody를 CreateToDoRequest에 넣어서 처리를 해준다.
def create_todo_handler(
request: CreateToDoRequest,
session: Session = Depends(get_db)
) -> ToDoSchema:
todo: ToDo=ToDo.create(request=request) # id=None
todo: ToDo=create_todo(session=session, todo=todo) # id=int
return ToDoSchema.model_validate(todo)
# orm.py
# 요청 받은 requestBody를 ORM 객체로 변환해주는 클래스 method
@classmethod
def create(cls, request : CreateToDoRequest) -> "ToDo":
return cls(
contents=request.contents,
is_done=request.is_done,
)
# test_main.py
def test_create_todo(client, mocker):
mocker.patch(
"main.create_todo",
return_value=ToDo(id=1, contents="todo", is_done=True),
)
body = {
"contents" : "test",
"is_done" : False,
}
response = client.post("/todos", json=body)
assert response.status_code == 201
assert response.json() == {"id": 1, "contents": "todo", "is_done": True}
- main.py의 create_todo를 mocking한다.
- post 요청으로 create를 해주기 때문에 requestbody가 필요하다.
- request.py 파일에 있는 객체 멤버변수인 contents, is_done 값을 body로 전달해준다.
- 이러면 pytest를 했을 때 pass는 나지만 뭔가 이상한 점이 발견된다.
이상한 점?
body = {
"contents" : "test",
"is_done" : False,
}
mocker.patch(
"main.create_todo",
return_value=ToDo(id=1, contents="todo", is_done=True),
)
...
assert response.json() == {"id": 1, "contents": "todo", "is_done": True}
- 지금 requestbody는 contents는 test, is_done값은 False값을 넘기고 있다.
- 하지만 마지막 response 데이터에서는 contents 값이 todo이고 is_done값이 True이다.
- 다시 말해서 이 response가 실제로 응답해서 그걸 검증하는 부분이 mocking 하는 부분과는 일치하지만 요청한 requestbody 부분과는 지금 일치하지 않는다.
- 하지만 마지막 response 데이터에서는 contents 값이 todo이고 is_done값이 True이다.
요청 흐름 시각화
client.post("/todos", json=body)
body = {"contents": "test", "is_done": False}
↓
create_todo_handler 실행
↓
ToDo.create(request=request) ← body로 ToDo 객체 생성
todo = ToDo(contents="test", is_done=False) id=None
↓
create_todo(session, todo) 호출 시도
↓
mocker가 가로챔
"DB에 저장하는 거 내가 대신할게"
return_value=ToDo(id=1, contents="todo", is_done=True)
↓
{"id": 1, "contents": "todo", "is_done": True} 반환
mocker가 대체하는 시점이 핵심
어디서 mocker가 개입하냐면
def create_todo_handler(request, session):
todo = ToDo.create(request=request)
# todo.contents = "test" ← body 값
# todo.is_done = False ← body 값
# todo.id = None ← 아직 DB 저장 전
todo = create_todo(session=session, todo=todo)
# ↑
# 여기서 mocker가 가로챔!
# body 값은 무시되고
# return_value 값이 반환됨
# todo.id = 1
# todo.contents = "todo"
# todo.is_done = True
return ToDoSchema.model_validate(todo)
# mocker가 준 값으로 응답
body랑 mocker 데이터가 달라도 pass인 이유
각 데이터의 역할
| 데이터 | 역할 | 응답에 영향? |
| body = {"contents": "test", "is_done": False} | create_todo 호출 전까지만 사용 | X |
| mocker return_value = ToDo(id=1, contents="todo", is_done=True) | create_todo 결과로 반환됨 | O |
body는 create_todo() 를 호출하는 데 쓰이는 값이고, mocker는 create_todo() 의 반환값을 대체하기 때문
이 테스트가 실제로 검증하는 것
이 테스트의 진짜 목적
# 이 테스트가 검증하는 것:
# 1. POST /todos 요청이 201 상태코드를 반환하는가?
assert response.status_code == 201
# 2. create_todo()의 반환값을 ToDoSchema로 잘 변환해서 응답하는가?
assert response.json() == {"id": 1, "contents": "todo", "is_done": True}
# 이 테스트가 검증하지 않는 것:
# - body 값이 실제 DB에 잘 저장되는가? (mocker로 대체했으니까)
# - contents="test", is_done=False 가 제대로 전달되는가?
💡 핵심 요약:
body 값은 create_todo() 를 호출할 때 쓰이는 입력값
mocker는 create_todo() 의 출력값을 대체
이 테스트는 "DB 저장이 잘 되는가" 가 아니라
"create_todo 결과를 받아서 응답을 잘 만드는가" 를 검증 것
mocker의 spy 사용해보기
def test_create_todo(client, mocker):
# ToDo라는 인자의 create라는 함수를 spy할 것이다. 라는 의미로 변수로 할당
create_spy = mocker.spy(ToDo, "create")
mocker.patch(
"main.create_todo",
return_value=ToDo(id=1, contents="todo", is_done=True),
)
body = {
"contents" : "test",
"is_done" : False,
}
response = client.post("/todos", json=body)
assert create_spy.spy_return.id is None
assert create_spy.spy_return.contents == "test"
assert create_spy.spy_return.is_done is False
assert response.status_code == 201
assert response.json() == {"id": 1, "contents": "todo", "is_done": True}
- spy_return을 할 때 ToDo와 연결이 되어 있다.
- 그래서 해당 ToDo 코드가 호출이 되면 return 값이 원래는 확인을 못했지만 spy를 사용하여 spy_return 안에 복사가 된다.
- 따라서 지금 이 spy 코드들은 ToDo가 잘 생성됐는지 검증해주는 코드
'FastAPI > 테스트 코드' 카테고리의 다른 글
| GET 단일 조회 API (0) | 2026.07.07 |
|---|---|
| PyTest Fixture (0) | 2026.07.07 |
| PyTest Mocking (0) | 2026.07.07 |
| GET 전체 조회 API (0) | 2026.07.06 |
| PyTest란? (0) | 2026.07.01 |