Test-Driven Development (TDD) with Python: Building a FastAPI Todo Application

IntroductionTest-Driven Development (TDD) is a software development approach that emphasizes writing tests before implementing the actual code.This article demonstrates TDD using Python, FastAPI, and pytest to build a Todo application with CRUD functio…


This content originally appeared on Level Up Coding - Medium and was authored by Rukshan J. Senanayaka

Introduction

Test-Driven Development (TDD) is a software development approach that emphasizes writing tests before implementing the actual code.

This article demonstrates TDD using Python, FastAPI, and pytest to build a Todo application with CRUD functionality. We’ll use Poetry for dependency management and PyCharm as our development environment. All of these tools are usable for pretty much any project, specially for the AI projects that are becoming increasingly popular these days.

The TDD Process

TDD follows a simple cycle as follows.

  1. Write a failing test
  2. Implement the minimum code to pass the test
  3. Refactor the code if necessary

This approach allows for writing robust applications. Although this might be a bit of overhead for very simple applications, when it comes to complex applications, TDD really makes your code clean, robust and saves a lot of time that you’d be otherwise spending debugging the code.

Let’s apply this process to our Todo application.

Prerequisites

If you are using the PyCharm IDE, the following steps can be easily completed in a single step, when creating a new project.

After initializing the new Fast API project, open a terminal and run the following to add the required dependencies for the tests.

poetry add pytest httpx

Building the Todo Application

Initialize “tests” folder

To use poetry to run our tests easily, we need to create a folder named “tests” within the root of our project and create a file named “__init__.py”. This file can be empty, but it needs to be present within the “tests” folder.

Create the deliberately failing tests for “create” operation

Create a test file named tests/test_todos.py Inside here we write the unit tests for the “create” operation.

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

client = TestClient(app)

def test_create_todo():
response = client.post("/todos/", json={"title": "Test Todo", "description": "Test Description", "completed": False})
print(response.content) # Keep this for debugging if needed
assert response.status_code == 201
assert response.json()["title"] == "Test Todo"
assert response.json()["description"] == "Test Description"
assert response.json()["completed"] == False
assert "id" in response.json()

Run the command below to see that the above test case is failing (this is expected no worries!)

poetry run pytest

You will get an output similar to below

The error logs show that we are expecting a 201 (CREATED) status code, but get a 404 (NOT FOUND). This is expected because we haven’t written the code to create a todo entry (and thereby return 201 status code) yet!

Now, implement the relevant “create” operation in main.py

from fastapi import FastAPI
from models import Todo

app = FastAPI()

todos = []

@app.post("/todos/", status_code=201, response_model=Todo)
async def create_todo(todo: Todo):
todo.id = len(todos) + 1
todos.append(todo)
return todo

We also need to create a model class to easily handle the fields for each todo entry.

To do this, create a file named models.py

from pydantic import BaseModel, Field

class Todo(BaseModel):
id: int = Field(default=None)
title: str
description: str
completed: bool

Import this class in the main.py as below.

from models import Todo

Now again execute the command to run the tests.

poetry run pytest

Now the output should be something like below, the test passes!

Implement the other CRUD Operations

Now, let’s implement the remaining operations namely the read,update and delete using TDD.

Thus, we are first writing the tests instead of the actual business logic code. For simplicity, we are just maintaining a python list as our datastore instead of an actual database.

In the sections a,b,c below, we would be writing the test cases for each of the CRUD operations. Then for each of the operation we would write the business logic to pass each of the test cases.

After you write a test case in a file, you can run the tests you’ve written this far by executing the command

pytest test_todos.py

a) Read Operation

Add the following test to tests/test_todos.py

def test_read_todo():
# First, create a todo
create_response = client.post("/todos/", json={"title": "Read Todo", "description": "Todo to be read", "completed": False})
todo_id = create_response.json()["id"]

# Now, read the todo
read_response = client.get(f"/todos/{todo_id}")
assert read_response.status_code == 200
assert read_response.json()["title"] == "Read Todo"
assert read_response.json()["description"] == "Todo to be read"
assert read_response.json()["completed"] == False
assert read_response.json()["id"] == todo_id

Implement the “read” operation in main.py

@app.get("/todos/{todo_id}", response_model=Todo)
async def read_todo(todo_id: int):
for todo in todos:
if todo.id == todo_id:
return todo
raise HTTPException(status_code=404, detail="Todo not found")

b) Update Operation

Add the following test to tests/test_todos.py

def test_update_todo():
# First, create a todo
create_response = client.post("/todos/", json={"title": "Update Todo", "description": "Todo to be updated", "completed": False})
todo_id = create_response.json()["id"]

# Now, update the todo
update_response = client.put(f"/todos/{todo_id}", json={"title": "Updated Todo", "description": "Todo has been updated", "completed": True})
assert update_response.status_code == 200
assert update_response.json()["title"] == "Updated Todo"
assert update_response.json()["description"] == "Todo has been updated"
assert update_response.json()["completed"] == True
assert update_response.json()["id"] == todo_id

Implement the “update” operation in main.py

@app.put("/todos/{todo_id}", response_model=Todo)
async def update_todo(todo_id: int, updated_todo: Todo):
for index, todo in enumerate(todos):
if todo.id == todo_id:
updated_todo.id = todo_id
todos[index] = updated_todo
return updated_todo
raise HTTPException(status_code=404, detail="Todo not found")

c) Delete Operation

Add the following test to tests/test_todos.py

def test_delete_todo():
# First, create a todo
create_response = client.post("/todos/", json={"title": "Delete Todo", "description": "Todo to be deleted", "completed": False})
todo_id = create_response.json()["id"]

# Now, delete the todo
delete_response = client.delete(f"/todos/{todo_id}")
assert delete_response.status_code == 204

# Verify that the todo is deleted
read_response = client.get(f"/todos/{todo_id}")
assert read_response.status_code == 404

Implement the “delete” operation in main.py

@app.delete("/todos/{todo_id}", status_code=204)
async def delete_todo(todo_id: int):
for index, todo in enumerate(todos):
if todo.id == todo_id:
todos.pop(index)
return
raise HTTPException(status_code=404, detail="Todo not found")

Running the Tests

To run the tests, execute the following command (that we earlier ran as well) in your terminal.

You can also run specific test files using the below,

pytest test_todos.py

Or else we can also run all the test cases that you’ve written by executing the following command,

poetry run pytest

This will run all the four tests we’ve written, ensuring that our Todo application functions as expected.

Source code

You can find the source code for the project at the following link.

https://github.com/RukshanJS/youtube-tdd-fast-api

Conclusion

In this article, we’ve demonstrated the Test-Driven Development process by building a Todo application using FastAPI and pytest. We covered all the basic CRUD operations, writing tests first and then implementing the functionality to pass those tests.

TDD helps ensure that our code is working as expected from the start and makes it easier to refactor and maintain our codebase. By following this approach, you can build more robust and reliable applications.

Remember to continue this cycle of writing tests, implementing code, and refactoring as you add more features to your application. Happy coding!


Test-Driven Development (TDD) with Python: Building a FastAPI Todo Application was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.


This content originally appeared on Level Up Coding - Medium and was authored by Rukshan J. Senanayaka


Print Share Comment Cite Upload Translate Updates
APA

Rukshan J. Senanayaka | Sciencx (2024-08-15T18:04:30+00:00) Test-Driven Development (TDD) with Python: Building a FastAPI Todo Application. Retrieved from https://www.scien.cx/2024/08/15/test-driven-development-tdd-with-python-building-a-fastapi-todo-application/

MLA
" » Test-Driven Development (TDD) with Python: Building a FastAPI Todo Application." Rukshan J. Senanayaka | Sciencx - Thursday August 15, 2024, https://www.scien.cx/2024/08/15/test-driven-development-tdd-with-python-building-a-fastapi-todo-application/
HARVARD
Rukshan J. Senanayaka | Sciencx Thursday August 15, 2024 » Test-Driven Development (TDD) with Python: Building a FastAPI Todo Application., viewed ,<https://www.scien.cx/2024/08/15/test-driven-development-tdd-with-python-building-a-fastapi-todo-application/>
VANCOUVER
Rukshan J. Senanayaka | Sciencx - » Test-Driven Development (TDD) with Python: Building a FastAPI Todo Application. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2024/08/15/test-driven-development-tdd-with-python-building-a-fastapi-todo-application/
CHICAGO
" » Test-Driven Development (TDD) with Python: Building a FastAPI Todo Application." Rukshan J. Senanayaka | Sciencx - Accessed . https://www.scien.cx/2024/08/15/test-driven-development-tdd-with-python-building-a-fastapi-todo-application/
IEEE
" » Test-Driven Development (TDD) with Python: Building a FastAPI Todo Application." Rukshan J. Senanayaka | Sciencx [Online]. Available: https://www.scien.cx/2024/08/15/test-driven-development-tdd-with-python-building-a-fastapi-todo-application/. [Accessed: ]
rf:citation
» Test-Driven Development (TDD) with Python: Building a FastAPI Todo Application | Rukshan J. Senanayaka | Sciencx | https://www.scien.cx/2024/08/15/test-driven-development-tdd-with-python-building-a-fastapi-todo-application/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.