Testing¶
korvex.testing runs the app in your tests, without a server:
Use this when you want to check your app with automated tests (pytest, for example). Here is a tiny app and its test:
from korvex import App, Form, HTTPException, Response, WebSocket
from korvex.middleware import current_session, sessions
from korvex.testing import TestClient
app = App()
sessions(app, secret_key="x" * 32) # a throwaway key, for the example
@app.post("/login")
def login(password: str = Form()):
if password != "s3cret":
raise HTTPException(401, "wrong password")
current_session()["user"] = "ada"
return Response.redirect("/", status=303)
@app.get("/api/tasks")
def tasks(done: bool = False):
if "user" not in current_session():
raise HTTPException(401, "log in first")
return Response.json([])
@app.websocket("/live")
async def live(ws: WebSocket):
await ws.accept()
await ws.send_json({"event": "presence"})
def test_tasks():
with TestClient(app) as client:
assert client.post("/login", form={"password": "s3cret"}).status == 303
response = client.get("/api/tasks", query={"done": "false"})
assert response.json() == [] # the session cookie was sent back
with client.websocket("/live") as ws:
assert ws.receive_json()["event"] == "presence"
With pytest¶
Put the client in a fixture, in conftest.py. The with block runs the
app's startup and shutdown hooks around each test:
import pytest
from korvex.testing import TestClient
from myapp import app # your code: where your App lives
@pytest.fixture
def client():
with TestClient(app) as client:
yield client
def test_tasks_need_login(client):
assert client.get("/api/tasks").status == 401
Requests¶
get(),post(),put(),patch(),delete(),head()andoptions()takequery=,headers=,cookies=, and a body:json=,form=(withfiles={"doc": ("a.pdf", data, "application/pdf")}for a multipart form), or rawbody=.- A response has
status,headers,body,text,json(), and theurlit came from. - Cookies the app sets are kept in
client.cookiesand sent back. follow_redirects=Truefollows redirects, up to 20 (after a 303, or a 301/302 answering a POST, as a GET).- A full URL sets the scheme and host:
client.get("https://acme.example.com/"). Otherwise requests go tobase_url,http://testserverby default. headers=andcookies=given to the client go with every request:TestClient(app, headers={"Authorization": "Bearer t0ken"}).
What the client does for you¶
- In a
withblock, the app's startup hooks run first and its shutdown hooks last; without one, no hook runs. Every request runs on the same event loop. - An error the app answers with a 500 is raised in the test, with its
traceback.
raise_server_errors=Falsegives you the 500 instead. - A request or WebSocket message not received within
timeout(5 seconds) raisesTimeoutError;timeout=Nonewaits forever.
Streams and WebSockets¶
# `client` is a TestClient, and /events an endpoint of yours
with client.stream("GET", "/events") as response:
first = next(response.iter_text()) # leaving the block stops the stream
To read a Server-Sent Events stream, iter_events() (aiter_events() on
AsyncTestClient) yields ServerSentEvents:
# /feed is an endpoint of yours that sends events
with client.stream("GET", "/feed") as response:
event = next(response.iter_events()) # event.event, event.data, event.id
client.websocket(path) opens one: send_text(), send_json()...,
receive_text(), receive_json()... A refused connection raises
WebSocketDisconnect. It takes query=, headers=, cookies= and
subprotocols=["chat"]; ws.subprotocol and ws.headers are those the
app accepted with.
Replacing a dependency¶
from korvex import App, Depends, Response
from korvex.testing import TestClient
app = App()
class User: # your code
def __init__(self, id: int, name: str):
self.id, self.name = id, name
def current_user() -> User: # your code: reads the session, a token...
raise NotImplementedError
@app.get("/me")
def me(user: User = Depends(current_user)):
return Response.json({"name": user.name})
app.dependency_overrides[current_user] = lambda: User(id=1, name="test")
Async tests¶
AsyncTestClient does the same, awaited, for async tests (pytest-asyncio
or anyio):
from korvex.testing import AsyncTestClient
async def test_live(): # app: the one above
async with AsyncTestClient(app) as client:
response = await client.get("/api/tasks")
assert response.status == 401
async with client.websocket("/live") as ws:
assert (await ws.receive_json())["event"] == "presence"
Next: The built-in server.