mirror of
https://github.com/kennethreitz/responder.git
synced 2026-06-05 14:50:19 +00:00
1bfd85b003
Define your API schemas with Pydantic models instead of (or alongside)
YAML docstrings and marshmallow:
from pydantic import BaseModel
class PetIn(BaseModel):
name: str
age: int = 0
class PetOut(BaseModel):
id: int
name: str
age: int
@api.route("/pets", methods=["POST"],
request_model=PetIn, response_model=PetOut)
async def create_pet(req, resp):
data = await req.media()
resp.media = {"id": 1, **data}
Also works with @api.schema("Name") decorator for registering
standalone schema components.
Pydantic models, marshmallow schemas, and YAML docstrings can all
be used together in the same API.
Also: rewrite docs with more prose, restore sidebar logo and links,
add FastAPI acknowledgment, update homepage copy.
161 tests, 95% coverage.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
158 lines
3.7 KiB
ReStructuredText
158 lines
3.7 KiB
ReStructuredText
Testing
|
|
=======
|
|
|
|
Responder includes a built-in test client powered by Starlette's
|
|
``TestClient``. You don't need to start a server — tests run in-process,
|
|
making them fast and reliable.
|
|
|
|
|
|
Getting Started
|
|
---------------
|
|
|
|
Given a simple application in ``api.py``::
|
|
|
|
import responder
|
|
|
|
api = responder.API()
|
|
|
|
@api.route("/")
|
|
def hello(req, resp):
|
|
resp.text = "hello, world!"
|
|
|
|
if __name__ == "__main__":
|
|
api.run()
|
|
|
|
You can test it with pytest::
|
|
|
|
# test_api.py
|
|
import api as service
|
|
|
|
def test_hello():
|
|
r = service.api.requests.get("/")
|
|
assert r.text == "hello, world!"
|
|
|
|
Run your tests::
|
|
|
|
$ pytest
|
|
|
|
|
|
Using Fixtures
|
|
--------------
|
|
|
|
For larger test suites, use pytest fixtures to share the API instance
|
|
across tests::
|
|
|
|
import pytest
|
|
import api as service
|
|
|
|
@pytest.fixture
|
|
def api():
|
|
return service.api
|
|
|
|
def test_hello(api):
|
|
r = api.requests.get("/")
|
|
assert r.text == "hello, world!"
|
|
|
|
def test_json(api):
|
|
@api.route("/data")
|
|
def data(req, resp):
|
|
resp.media = {"key": "value"}
|
|
|
|
r = api.requests.get(api.url_for(data))
|
|
assert r.json() == {"key": "value"}
|
|
|
|
The ``api.url_for()`` method generates a URL for a given route endpoint,
|
|
so you don't have to hard-code paths in your tests.
|
|
|
|
|
|
Testing JSON APIs
|
|
-----------------
|
|
|
|
Send JSON data and check the response::
|
|
|
|
def test_create_item(api):
|
|
@api.route("/items")
|
|
async def create(req, resp):
|
|
data = await req.media()
|
|
resp.media = {"created": data}
|
|
resp.status_code = 201
|
|
|
|
r = api.requests.post(api.url_for(create), json={"name": "widget"})
|
|
assert r.status_code == 201
|
|
assert r.json() == {"created": {"name": "widget"}}
|
|
|
|
|
|
Testing File Uploads
|
|
--------------------
|
|
|
|
Send files using the ``files`` parameter::
|
|
|
|
def test_upload(api):
|
|
@api.route("/upload")
|
|
async def upload(req, resp):
|
|
files = await req.media("files")
|
|
resp.media = {"received": list(files.keys())}
|
|
|
|
files = {"doc": ("report.pdf", b"content", "application/pdf")}
|
|
r = api.requests.post(api.url_for(upload), files=files)
|
|
assert r.json() == {"received": ["doc"]}
|
|
|
|
|
|
Testing WebSockets
|
|
------------------
|
|
|
|
Use Starlette's ``TestClient`` directly for WebSocket connections::
|
|
|
|
from starlette.testclient import TestClient
|
|
|
|
def test_websocket(api):
|
|
@api.route("/ws", websocket=True)
|
|
async def ws(ws):
|
|
await ws.accept()
|
|
await ws.send_text("hello")
|
|
await ws.close()
|
|
|
|
client = TestClient(api)
|
|
with client.websocket_connect("/ws") as ws:
|
|
assert ws.receive_text() == "hello"
|
|
|
|
|
|
Testing Error Handling
|
|
----------------------
|
|
|
|
To test error responses without pytest raising the exception, disable
|
|
server exception propagation::
|
|
|
|
from starlette.testclient import TestClient
|
|
|
|
def test_500(api):
|
|
@api.route("/fail")
|
|
def fail(req, resp):
|
|
raise ValueError("something broke")
|
|
|
|
client = TestClient(api, raise_server_exceptions=False)
|
|
r = client.get(api.url_for(fail))
|
|
assert r.status_code == 500
|
|
|
|
|
|
Testing Lifespan Events
|
|
-----------------------
|
|
|
|
The test client supports lifespan events. Use ``with`` to ensure startup
|
|
and shutdown hooks run::
|
|
|
|
def test_with_lifespan(api):
|
|
started = {"value": False}
|
|
|
|
@api.on_event("startup")
|
|
async def on_startup():
|
|
started["value"] = True
|
|
|
|
@api.route("/")
|
|
def check(req, resp):
|
|
resp.media = {"started": started["value"]}
|
|
|
|
with api.requests as session:
|
|
r = session.get("http://;/")
|
|
assert r.json() == {"started": True}
|