DEV Community

Cover image for FastAPI Tips & Tricks: Testing a Database

FastAPI Tips & Tricks: Testing a Database

JB on October 25, 2021

If you haven't heard of it yet, FastAPI is a micro-framewok that allows developers to make full use of modern Python."Micro" here, means that rathe...
Collapse
 
capaci profile image
Rafael Capaci

was struglying setting up pytest and your post helped a lot. Thanks for that!

Collapse
 
bfontaine profile image
Baptiste Fontaine • Edited

The issue with this setup is that the same database session is used for all calls made within a test, meanwhile in a normal workflow a new session is created for each call. This makes tests fail for cases that otherwise work if tested with curl.
I’m not sure how to make this work yet.

Edit: I ended up re-creating the database for each test: stackoverflow.com/a/76013246/735926

Collapse
 
electronicmoney profile image
Emeka Augustine

Briliant!

Collapse
 
jbrocher profile image
JB

Thanks :D

Collapse
 
tur8008 profile image
Artur Ivanov

Thank you! Brilliant job! Did my week!

Collapse
 
jbrocher profile image
JB

Thanks Artur ! Knowing that I helped someone on their programming journey always makes my day :D

Collapse
 
julienv profile image
Julien Vonthron

Hi JB,
I have an issue with this method, it is that for some reason, it doesn't work with post tests, if you use the 'items' fixture in argument

for exemple, let's say you write this test:

def test_post_items(items, client):
    response = client.post("/items", json={'title': "item 1"})
    assert response.status_code == 200
Enter fullscreen mode Exit fullscreen mode

in my case, sqlachemy complains with a duplicate key id error. If i check the db, the items_seq_id is in fact not updated. Works fine if i don't put the 'items' fixture in argument of the test though...

Do you see any reason why ? Did you never run into such issue ?

Collapse
 
razvantudorica profile image
RΔƒzvan TudoricΔƒ • Edited

a bit cumbersome to pass in ALL the methods the db: Session = Depends(get_db) parameter.

Collapse
 
pshrest2 profile image
Pranaya Shrestha

You can also use class based views from FastAPI-RESTful to solve this
fastapi-restful.netlify.app/user-g...

Collapse
 
bfontaine profile image
Baptiste Fontaine

Starting with FastAPI 0.95 you can greatly simplify this:

from typing import Annotated

DBSession = Annotated[Session, Depends(get_db)]

@app.get("/foo")
async def get_foo(db: DBSession):
    ...
Enter fullscreen mode Exit fullscreen mode
Collapse
 
jbrocher profile image
JB

Well, it's not aboslutely necessary to do it. You could probably just create a db module to instantiate the session. Because of python import cache, in most cases this would essentially create a singleton. Then you can monkeypatch it using the monkeypatch fixture provided by pytest

However using fastapi dependency injection system is the recommended way to do it. This because it comes with a bunch of really useful features including but not limited to:

  • Automatic dependency resolution, that guarantee that each dependency is executed only once per request. So if you want to do some request-dependant processing while you instantiate the session, you can use a dependency, and go on about your day.
  • Pre / Post processing. For example in the post, I'm using it to close the session automatically after processing the resquest. Without it you would have to repeat this every time you use the session. This also means you would have keep track of which endpoint eventually end up using the session. (How to put it an other way, endpoints that are depending on it ;) )

So this is a nice way to decouple your business logic from the request execution path. Without it you'd be force to organize you code around it. This is basically what makes FastAPI a framework and not a library!

Collapse
 
arielpontes profile image
Ariel Pontes

Where does the value of database_exists come from?

Collapse
 
sergiohcosta profile image
Sergio Costa
Collapse
 
changocoder profile image
Hugo Ch.

you need to install this requirement sqlalchemy_utils

Collapse
 
bfontaine profile image
Baptiste Fontaine

Thank you for this post. Is it normal that the transaction variable in transaction = connection.begin() is never used?

Collapse
 
jbrocher profile image
JB • Edited

Nope you're right it's never being used ! Sometimes I test different configurations when writing a post, and it's hard keeping the code sample exactly in sync whit what I'm writing. This is a part of my workflow I need to improve :D

The transaction variable should actually be used for the rollback instead of the session.

Thanks for pointing it out, I'll correct it :)