* Add an example of setting up a test database.
* :memo: Add/update docs for testing a DB with dependency overrides
* :wrench: Update test script, remove line removing test file as it is removed during testing
* :white_check_mark: Update testing coverage pragma
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
--- /dev/null
+# Testing a Database
+
+You can use the same dependency overrides from [Testing Dependencies with Overrides](testing-dependencies.md){.internal-link target=_blank} to alter a database for testing.
+
+You could want to set up a different database for testing, rollback the data after the tests, pre-fill it with some testing data, etc.
+
+The main idea is exactly the same you saw in that previous chapter.
+
+## Add tests for the SQL app
+
+Let's update the example from [SQL (Relational) Databases](../tutorial/sql-databases.md){.internal-link target=_blank} to use a testing database.
+
+All the app code is the same, you can go back to that chapter check how it was.
+
+The only changes here are in the new testing file.
+
+Your normal dependency `get_db()` would return a database session.
+
+In the test, you could use a dependency override to return your *custom* database session instead of the one that would be used normally.
+
+In this example we'll create a temporary database only for the tests.
+
+## File structure
+
+We create a new file at `sql_app/tests/test_sql_app.py`.
+
+So the new file structure looks like:
+
+``` hl_lines="9 10 11"
+.
+└── sql_app
+ ├── __init__.py
+ ├── crud.py
+ ├── database.py
+ ├── main.py
+ ├── models.py
+ ├── schemas.py
+ └── tests
+ ├── __init__.py
+ └── test_sql_app.py
+```
+
+## Create the new database session
+
+First, we create a new database session with the new database.
+
+For the tests we'll use a file `test.db` instead of `sql_app.db`.
+
+But the rest of the session code is more or less the same, we just copy it.
+
+```Python hl_lines="8 9 10 11 12 13"
+{!../../../docs_src/sql_databases/sql_app/tests/test_sql_app.py!}
+```
+
+!!! tip
+ You could reduce duplication in that code by putting it in a function and using it from both `database.py` and `tests/test_sql_app.py`.
+
+ For simplicity and to focus on the specific testing code, we are just copying it.
+
+## Create the database
+
+Because now we are going to use a new database in a new file, we need to make sure we create the database with:
+
+```Python
+Base.metadata.create_all(bind=engine)
+```
+
+That is normally called in `main.py`, but the line in `main.py` uses the database file `sql_app.db`, and we need to make sure we create `test.db` for the tests.
+
+So we add that line here, with the new file.
+
+```Python hl_lines="16"
+{!../../../docs_src/sql_databases/sql_app/tests/test_sql_app.py!}
+```
+
+## Dependency override
+
+Now we create the dependency override and add it to the overrides for our app.
+
+```Python hl_lines="19 20 21 22 23 24 27"
+{!../../../docs_src/sql_databases/sql_app/tests/test_sql_app.py!}
+```
+
+!!! tip
+ The code for `override_get_db()` is almost exactly the same as for `get_db()`, but in `override_get_db()` we use the `TestingSessionLocal` for the testing database instead.
+
+## Test the app
+
+Then we can just test the app as normally.
+
+```Python hl_lines="32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47"
+{!../../../docs_src/sql_databases/sql_app/tests/test_sql_app.py!}
+```
+
+And all the modifications we made in the database during the tests will be in the `test.db` database instead of the main `sql_app.db`.
In this case, you can override the dependency that calls that provider, and use a custom dependency that returns a mock user, only for your tests.
-### Use case: testing database
-
-Other example could be that you are using a specific database only for testing.
-
-Your normal dependency would return a database session.
-
-But then, after each test, you could want to rollback all the operations or remove data.
-
-Or you could want to alter the data before the tests run, etc.
-
-In this case, you could use a dependency override to return your *custom* database session instead of the one that would be used normally.
-
### Use the `app.dependency_overrides` attribute
For these cases, your **FastAPI** application has an attribute `app.dependency_overrides`, it is a simple `dict`.
In this example, we are "connecting" to a SQLite database (opening a file with the SQLite database).
-The file will be located at the same directory in the file `test.db`.
+The file will be located at the same directory in the file `sql_app.db`.
-That's why the last part is `./test.db`.
+That's why the last part is `./sql_app.db`.
If you were using a **PostgreSQL** database instead, you would just have to uncomment the line:
- advanced/testing-websockets.md
- advanced/testing-events.md
- advanced/testing-dependencies.md
+ - advanced/testing-database.md
- advanced/settings.md
- advanced/extending-openapi.md
- advanced/openapi-callbacks.md
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
-SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
+SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
engine = create_engine(
--- /dev/null
+from fastapi.testclient import TestClient
+from sqlalchemy import create_engine
+from sqlalchemy.orm import sessionmaker
+
+from ..database import Base
+from ..main import app, get_db
+
+SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
+
+engine = create_engine(
+ SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
+)
+TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
+
+
+Base.metadata.create_all(bind=engine)
+
+
+def override_get_db():
+ try:
+ db = TestingSessionLocal()
+ yield db
+ finally:
+ db.close()
+
+
+app.dependency_overrides[get_db] = override_get_db
+
+client = TestClient(app)
+
+
+def test_create_user():
+ response = client.post(
+ "/users/",
+ json={"email": "deadpool@example.com", "password": "chimichangas4life"},
+ )
+ assert response.status_code == 200
+ data = response.json()
+ assert data["email"] == "deadpool@example.com"
+ assert "id" in data
+ user_id = data["id"]
+
+ response = client.get(f"/users/{user_id}")
+ assert response.status_code == 200
+ data = response.json()
+ assert data["email"] == "deadpool@example.com"
+ assert data["id"] == user_id
set -e
set -x
-# Remove temporary DB
-if [ -f ./test.db ]; then
- rm ./test.db
-fi
bash ./scripts/lint.sh
# Check README.md is up to date
diff --brief docs/en/docs/index.md README.md
# Import while creating the client to create the DB after starting the test session
from sql_databases.sql_app.main import app
- test_db = Path("./test.db")
+ test_db = Path("./sql_app.db")
with TestClient(app) as c:
yield c
test_db.unlink()
# Import while creating the client to create the DB after starting the test session
from sql_databases.sql_app.alt_main import app
- test_db = Path("./test.db")
+ test_db = Path("./sql_app.db")
with TestClient(app) as c:
yield c
test_db.unlink()
--- /dev/null
+from pathlib import Path
+
+
+def test_testing_dbs():
+ # Import while creating the client to create the DB after starting the test session
+ from sql_databases.sql_app.tests.test_sql_app import test_create_user
+
+ test_db = Path("./test.db")
+ app_db = Path("./sql_app.db")
+ test_create_user()
+ test_db.unlink()
+ if app_db.is_file(): # pragma: nocover
+ app_db.unlink()