]> git.ipfire.org Git - thirdparty/fastapi/fastapi.git/commitdiff
:memo: Add an example of setting up a test database (#1144)
authorduganchen <duganchen@users.noreply.github.com>
Sun, 5 Apr 2020 11:53:09 +0000 (04:53 -0700)
committerGitHub <noreply@github.com>
Sun, 5 Apr 2020 11:53:09 +0000 (13:53 +0200)
* 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>
docs/en/docs/advanced/testing-database.md [new file with mode: 0644]
docs/en/docs/advanced/testing-dependencies.md
docs/en/docs/tutorial/sql-databases.md
docs/en/mkdocs.yml
docs_src/sql_databases/sql_app/database.py
docs_src/sql_databases/sql_app/tests/__init__.py [new file with mode: 0644]
docs_src/sql_databases/sql_app/tests/test_sql_app.py [new file with mode: 0644]
scripts/test.sh
tests/test_tutorial/test_sql_databases/test_sql_databases.py
tests/test_tutorial/test_sql_databases/test_sql_databases_middleware.py
tests/test_tutorial/test_sql_databases/test_testing_databases.py [new file with mode: 0644]

diff --git a/docs/en/docs/advanced/testing-database.md b/docs/en/docs/advanced/testing-database.md
new file mode 100644 (file)
index 0000000..385d988
--- /dev/null
@@ -0,0 +1,95 @@
+# 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`.
index 5ba7e219b9b763f7bf581a51a4626ee31a16a60f..f2c283947ff834fbee7cf8916cccdd91a06e6506 100644 (file)
@@ -20,18 +20,6 @@ You probably want to test the external provider once, but not necessarily call i
 
 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`.
index 179e75f0a9ec6fb5c1449756f1346c37320bc921..726acc7c46c343954b393f2700fd54d97188459b 100644 (file)
@@ -98,9 +98,9 @@ Let's refer to the file `sql_app/database.py`.
 
 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:
 
index 3a0dfbd7f07173ec71b5a2edb311f2fa86741872..717dc8e3c2a8584f0e3418f7f6ee66b8b623c447 100644 (file)
@@ -99,6 +99,7 @@ nav:
   - 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
index 73fc456a24c267f7845a292978e5ee367f06bf2f..45a8b9f6942b32c53b5287ab19d6f581effb9fe3 100644 (file)
@@ -2,7 +2,7 @@ from sqlalchemy import create_engine
 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(
diff --git a/docs_src/sql_databases/sql_app/tests/__init__.py b/docs_src/sql_databases/sql_app/tests/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/docs_src/sql_databases/sql_app/tests/test_sql_app.py b/docs_src/sql_databases/sql_app/tests/test_sql_app.py
new file mode 100644 (file)
index 0000000..01c11a8
--- /dev/null
@@ -0,0 +1,47 @@
+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
index 468b2c6674c83eb8da08151a12ab731b4c888ebd..54fd7410bc309ac1ddc2467bd3e9d9b7d468f710 100755 (executable)
@@ -3,10 +3,6 @@
 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
index acafa011c29a312cb08c6bc04967184062e413ca..b791ea386e15a55ce91774b5045e9a7f0a392543 100644 (file)
@@ -286,7 +286,7 @@ def client():
     # 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()
index 43bc87657c23d73fe53492fa5785eb82cc0a980c..96b48874d4a4be35be81569b00101f9ca39178a0 100644 (file)
@@ -286,7 +286,7 @@ def client():
     # 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()
diff --git a/tests/test_tutorial/test_sql_databases/test_testing_databases.py b/tests/test_tutorial/test_sql_databases/test_testing_databases.py
new file mode 100644 (file)
index 0000000..fae66d3
--- /dev/null
@@ -0,0 +1,13 @@
+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()