]> git.ipfire.org Git - thirdparty/fastapi/fastapi.git/commitdiff
:sparkles: Add testing docs and tests (#151)
authorSebastián Ramírez <tiangolo@gmail.com>
Fri, 12 Apr 2019 16:15:05 +0000 (20:15 +0400)
committerGitHub <noreply@github.com>
Fri, 12 Apr 2019 16:15:05 +0000 (20:15 +0400)
* :pencil2: Fix typo in security intro

* :sparkles: Add testing docs and tests

* :bug: Debug Travis coverage

* :bug: Debug Travis coverage, report XML

* :green_heart: Make Travis/Flit use same code install

* :rewind: Revert Travis/Codecov debugging changes

17 files changed:
.travis.yml
docs/src/app_testing/__init__.py [new file with mode: 0644]
docs/src/app_testing/main.py [new file with mode: 0644]
docs/src/app_testing/test_main.py [new file with mode: 0644]
docs/src/app_testing/tutorial001.py [new file with mode: 0644]
docs/src/app_testing/tutorial002.py [new file with mode: 0644]
docs/src/app_testing/tutorial003.py [new file with mode: 0644]
docs/src/events/tutorial001.py
docs/tutorial/security/intro.md
docs/tutorial/testing.md [new file with mode: 0644]
mkdocs.yml
tests/test_tutorial/test_events/test_tutorial001.py
tests/test_tutorial/test_testing/__init__.py [new file with mode: 0644]
tests/test_tutorial/test_testing/test_main.py [new file with mode: 0644]
tests/test_tutorial/test_testing/test_tutorial001.py [new file with mode: 0644]
tests/test_tutorial/test_testing/test_tutorial002.py [new file with mode: 0644]
tests/test_tutorial/test_testing/test_tutorial003.py [new file with mode: 0644]

index e97301248a7b566b17c6c7644e0a76130014f6ce..c37021e9368d82c272001a775299797568004229 100644 (file)
@@ -10,7 +10,7 @@ python:
 
 install:
     - pip install flit
-    - flit install
+    - flit install --symlink
 
 script:
     - bash scripts/test.sh
diff --git a/docs/src/app_testing/__init__.py b/docs/src/app_testing/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/docs/src/app_testing/main.py b/docs/src/app_testing/main.py
new file mode 100644 (file)
index 0000000..4679aec
--- /dev/null
@@ -0,0 +1,8 @@
+from fastapi import FastAPI
+
+app = FastAPI()
+
+
+@app.get("/")
+async def read_main():
+    return {"msg": "Hello World"}
diff --git a/docs/src/app_testing/test_main.py b/docs/src/app_testing/test_main.py
new file mode 100644 (file)
index 0000000..b02d7f0
--- /dev/null
@@ -0,0 +1,11 @@
+from starlette.testclient import TestClient
+
+from .main import app
+
+client = TestClient(app)
+
+
+def test_read_main():
+    response = client.get("/")
+    assert response.status_code == 200
+    assert response.json() == {"msg": "Hello World"}
diff --git a/docs/src/app_testing/tutorial001.py b/docs/src/app_testing/tutorial001.py
new file mode 100644 (file)
index 0000000..9b00acb
--- /dev/null
@@ -0,0 +1,18 @@
+from fastapi import FastAPI
+from starlette.testclient import TestClient
+
+app = FastAPI()
+
+
+@app.get("/")
+async def read_main():
+    return {"msg": "Hello World"}
+
+
+client = TestClient(app)
+
+
+def test_read_main():
+    response = client.get("/")
+    assert response.status_code == 200
+    assert response.json() == {"msg": "Hello World"}
diff --git a/docs/src/app_testing/tutorial002.py b/docs/src/app_testing/tutorial002.py
new file mode 100644 (file)
index 0000000..0c3b9b9
--- /dev/null
@@ -0,0 +1,31 @@
+from fastapi import FastAPI
+from starlette.testclient import TestClient
+from starlette.websockets import WebSocket
+
+app = FastAPI()
+
+
+@app.get("/")
+async def read_main():
+    return {"msg": "Hello World"}
+
+
+@app.websocket_route("/ws")
+async def websocket(websocket: WebSocket):
+    await websocket.accept()
+    await websocket.send_json({"msg": "Hello WebSocket"})
+    await websocket.close()
+
+
+def test_read_main():
+    client = TestClient(app)
+    response = client.get("/")
+    assert response.status_code == 200
+    assert response.json() == {"msg": "Hello World"}
+
+
+def test_websocket():
+    client = TestClient(app)
+    with client.websocket_connect("/ws") as websocket:
+        data = websocket.receive_json()
+        assert data == {"msg": "Hello WebSocket"}
diff --git a/docs/src/app_testing/tutorial003.py b/docs/src/app_testing/tutorial003.py
new file mode 100644 (file)
index 0000000..8843de1
--- /dev/null
@@ -0,0 +1,24 @@
+from fastapi import FastAPI
+from starlette.testclient import TestClient
+
+app = FastAPI()
+
+items = {}
+
+
+@app.on_event("startup")
+async def startup_event():
+    items["foo"] = {"name": "Fighters"}
+    items["bar"] = {"name": "Tenders"}
+
+
+@app.get("/items/{item_id}")
+async def read_items(item_id: str):
+    return items[item_id]
+
+
+def test_read_items():
+    with TestClient(app) as client:
+        response = client.get("/items/foo")
+        assert response.status_code == 200
+        assert response.json() == {"name": "Fighters"}
index 6a0bd5a20dcb1d2445acc8054c95439cdea11e89..128004c9fb65ff35d84a97820b6c0d62d537a6e4 100644 (file)
@@ -12,5 +12,5 @@ async def startup_event():
 
 
 @app.get("/items/{item_id}")
-async def read_item(item_id: str):
+async def read_items(item_id: str):
     return items[item_id]
index 0ec81dbd7fe8f5d17c8c9c28b685be78dd8c4fd8..74330a0a2eae7c5c7d02cee32fc50f625bb4a43c 100644 (file)
@@ -20,7 +20,7 @@ It is quite an extensive specification and covers several complex use cases.
 
 It includes ways to authenticate using a "third party".
 
-That's what all the system with "login with Facebook, Google, Twitter, GitHub" use underneath.
+That's what all the systems with "login with Facebook, Google, Twitter, GitHub" use underneath.
 
 ### OAuth 1
 
diff --git a/docs/tutorial/testing.md b/docs/tutorial/testing.md
new file mode 100644 (file)
index 0000000..dcb50ab
--- /dev/null
@@ -0,0 +1,69 @@
+Thanks to <a href="https://www.starlette.io/testclient/" target="_blank">Starlette's TestClient</a>, testing **FastAPI** applications is easy and enjoyable.
+
+It is based on <a href="http://docs.python-requests.org" target="_blank">Requests</a>, so it's very familiar and intuitive.
+
+With it, you can use <a href="https://docs.pytest.org/" target="_blank">pytest</a> directly with **FastAPI**.
+
+## Using `TestClient`
+
+Import `TestClient` from `starlette.testclient`.
+
+Create a `TestClient` passing to it your **FastAPI**.
+
+Create functions with a name that starts with `test_` (this is standard `pytest` conventions).
+
+Use the `TestClient` object the same way as you do with `requests`.
+
+Write simple `assert` statements with the standard Python expressions that you need to check (again, standard `pytest`).
+
+```Python hl_lines="2 12 15 16 17 18"
+{!./src/app_testing/tutorial001.py!}
+```
+
+!!! tip
+    Notice that the testing functions are normal `def`, not `async def`. 
+    
+    And the calls to the client are also normal calls, not using `await`.
+
+    This allows you to use `pytest` directly without complications.
+
+
+## Separating tests
+
+In a real application, you probably would have your tests in a different file.
+
+And your **FastAPI** application might also be composed of several files/modules, etc.
+
+### **FastAPI** app file
+
+Let's say you have a file `main.py` with your **FastAPI** app:
+
+```Python
+{!./src/app_testing/main.py!}
+```
+
+### Testing file
+
+Then you could have a file `test_main.py` with your tests, and import your `app` from the `main` module (`main.py`):
+
+```Python
+{!./src/app_testing/test_main.py!}
+```
+
+## Testing WebSockets
+
+You can use the same `TestClient` to test WebSockets.
+
+For this, you use the `TestClient` in a `with` statement, connecting to the WebSocket:
+
+```Python hl_lines="27 28 29 30 31"
+{!./src/app_testing/tutorial002.py!}
+```
+
+## Testing Events, `startup` and `shutdown`
+
+When you need your event handlers (`startup` and `shutdown`) to run in your tests, you can use the `TestClient` with a `with` statement:
+
+```Python hl_lines="9 10 11 12 20 21 22 23 24"
+{!./src/app_testing/tutorial003.py!}
+```
index d7cf5f2422c298e1b361a108e41ca51f9465e881..ef463f38bcb0052c0efde482e16bb936d06cf0b7 100644 (file)
@@ -68,6 +68,7 @@ nav:
         - GraphQL: 'tutorial/graphql.md'
         - WebSockets: 'tutorial/websockets.md'
         - 'Events: startup - shutdown': 'tutorial/events.md'
+        - Testing: 'tutorial/testing.md'
         - Debugging: 'tutorial/debugging.md'
         - Extending OpenAPI: 'tutorial/extending-openapi.md'
     - Concurrency and async / await: 'async.md'
index 2b05f13754104c58b86b38272661442623fb03de..2fedbc3c3f8d77b5ca83993bd1db1eca87ed5554 100644 (file)
@@ -24,8 +24,8 @@ openapi_schema = {
                         },
                     },
                 },
-                "summary": "Read Item Get",
-                "operationId": "read_item_items__item_id__get",
+                "summary": "Read Items Get",
+                "operationId": "read_items_items__item_id__get",
                 "parameters": [
                     {
                         "required": True,
diff --git a/tests/test_tutorial/test_testing/__init__.py b/tests/test_tutorial/test_testing/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/test_tutorial/test_testing/test_main.py b/tests/test_tutorial/test_testing/test_main.py
new file mode 100644 (file)
index 0000000..cdd4bbc
--- /dev/null
@@ -0,0 +1,30 @@
+from app_testing.test_main import client, test_read_main
+
+openapi_schema = {
+    "openapi": "3.0.2",
+    "info": {"title": "Fast API", "version": "0.1.0"},
+    "paths": {
+        "/": {
+            "get": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {"application/json": {"schema": {}}},
+                    }
+                },
+                "summary": "Read Main Get",
+                "operationId": "read_main__get",
+            }
+        }
+    },
+}
+
+
+def test_openapi_schema():
+    response = client.get("/openapi.json")
+    assert response.status_code == 200
+    assert response.json() == openapi_schema
+
+
+def test_main():
+    test_read_main()
diff --git a/tests/test_tutorial/test_testing/test_tutorial001.py b/tests/test_tutorial/test_testing/test_tutorial001.py
new file mode 100644 (file)
index 0000000..a0b8df4
--- /dev/null
@@ -0,0 +1,30 @@
+from app_testing.tutorial001 import client, test_read_main
+
+openapi_schema = {
+    "openapi": "3.0.2",
+    "info": {"title": "Fast API", "version": "0.1.0"},
+    "paths": {
+        "/": {
+            "get": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {"application/json": {"schema": {}}},
+                    }
+                },
+                "summary": "Read Main Get",
+                "operationId": "read_main__get",
+            }
+        }
+    },
+}
+
+
+def test_openapi_schema():
+    response = client.get("/openapi.json")
+    assert response.status_code == 200
+    assert response.json() == openapi_schema
+
+
+def test_main():
+    test_read_main()
diff --git a/tests/test_tutorial/test_testing/test_tutorial002.py b/tests/test_tutorial/test_testing/test_tutorial002.py
new file mode 100644 (file)
index 0000000..c1e1e4e
--- /dev/null
@@ -0,0 +1,9 @@
+from app_testing.tutorial002 import test_read_main, test_websocket
+
+
+def test_main():
+    test_read_main()
+
+
+def test_ws():
+    test_websocket()
diff --git a/tests/test_tutorial/test_testing/test_tutorial003.py b/tests/test_tutorial/test_testing/test_tutorial003.py
new file mode 100644 (file)
index 0000000..074c42a
--- /dev/null
@@ -0,0 +1,5 @@
+from app_testing.tutorial003 import test_read_items
+
+
+def test_main():
+    test_read_items()