测试

Batman 希望在每次运行测试时无需启动完整服务器。Robyn 为他提供了内置的 TestClient —— 一个轻量的进程内测试客户端,它直接执行路由处理器,使测试运行快速且具有确定性。

快速上手

TestClient 包装了一个 Robyn 应用,允许你像发起真实 HTTP 请求一样调用路由——但这一切都在进程内完成。无需端口、无需套接字、无需启动服务器。

robyn.testing 导入 TestClient,将你的应用传入它,然后开始发起请求:

Request

GET
/hello
from robyn import Robyn
from robyn.testing import TestClient

app = Robyn(__file__)

@app.get("/hello")
def hello(request):
    return "Hello, World!"

client = TestClient(app)

def test_hello():
    response = client.get("/hello")
    assert response.status_code == 200
    assert response.text == "Hello, World!"

TestResponse 对象

每个请求方法都会返回一个 TestResponse,包含以下属性:

PropertyTypeDescription
status_codeintHTTP 状态码
textstr解码后的响应主体字符串
contentbytes原始响应主体(字节)
headersHeaders响应头
okbool如果状态为 2xx 则为 True

TestResponse 还提供 .json() 方法用于将响应体解析为 JSON。

Request

GET
/users
@app.get("/users")
def get_users(request):
    return [{"name": "Batman"}, {"name": "Robin"}]

def test_json_response():
    response = client.get("/users")
    assert response.ok
    data = response.json()
    assert len(data) == 2
    assert data[0]["name"] == "Batman"

HTTP 方法

TestClient 支持所有常见的 HTTP 方法。通常会发送请求体的方法(如 POSTPUTPATCHDELETE)提供了 json_data 参数以便快捷发送 JSON。

使用 json_data 发送 JSON 负载——客户端会自动设置 Content-Type: application/json 并序列化数据:

Request

POST
/items
@app.post("/items")
def create_item(request):
    body = request.json()
    return {"id": 1, "name": body["name"]}

def test_post_json():
    response = client.post("/items", json_data={"name": "Batarang"})
    assert response.status_code == 200
    assert response.json()["name"] == "Batarang"

你也可以发送原始字符串或字节体、自定义头、查询参数、表单数据和文件:

Request

POST
/upload
def test_with_all_options():
    response = client.post(
        "/search",
        body="raw body content",
        headers={"X-Custom": "value"},
        query_params={"q": "batman"},
    )
    assert response.ok

路径参数

带路径参数的路由在测试环境中与生产环境行为一致。TestClient 会匹配路由模式并自动提取参数。

路径参数会从 URL 中解析,并通过 Robyn 的参数解析管道传入处理函数:

Request

GET
/users/:user_id
@app.get("/users/:user_id")
def get_user(request, user_id: int):
    return {"user_id": user_id}

def test_path_params():
    response = client.get("/users/42")
    assert response.json()["user_id"] == 42

测试中间件

TestClient 会在相同顺序中复现完整的请求管道——包括前置中间件、处理器、全局响应头和后置中间件,行为与 Rust 运行时一致。

会修改请求或响应的中间件在测试中会如同生产环境一样执行:

Request

GET
/protected
@app.before_request()
def add_request_id(request):
    request.headers.set("X-Request-ID", "test-123")
    return request

@app.after_request()
def add_server_header(response):
    response.headers.set("X-Server", "Robyn")
    return response

@app.get("/protected")
def protected(request):
    return request.headers.get("X-Request-ID")

def test_middleware_pipeline():
    response = client.get("/protected")
    assert response.text == "test-123"
    assert response.headers.get("X-Server") == "Robyn"

作为上下文管理器使用

TestClient 实现了上下文管理协议。与 with 一起使用时,内部事件循环会自动清理:

Request

GET
/
def test_with_context_manager():
    with TestClient(app) as client:
        response = client.get("/hello")
        assert response.ok
    # 事件循环在此处关闭

使用 pytest 运行测试

由于 TestClient 不会启动服务器,测试运行速度与普通单元测试相同。直接使用 pytest 即可——不需要特殊插件或 fixture。

一个典型的测试文件:

Test File

TEST
test_app.py
import pytest
from robyn import Robyn
from robyn.testing import TestClient

app = Robyn(__file__)

@app.get("/")
def index(request):
    return "Home"

@app.get("/health")
def health(request):
    return {"status": "ok"}

@app.post("/echo")
def echo(request):
    return request.json()

client = TestClient(app)

def test_index():
    assert client.get("/").text == "Home"

def test_health():
    data = client.get("/health").json()
    assert data["status"] == "ok"

def test_echo():
    payload = {"message": "hello"}
    response = client.post("/echo", json_data=payload)
    assert response.json() == payload

def test_not_found():
    response = client.get("/nonexistent")
    assert response.status_code == 404

Run with:

pytest test_app.py -v

可用方法

MethodSignature
client.get(path, **kw)GET 请求
client.post(path, json_data=None, **kw)POST 请求
client.put(path, json_data=None, **kw)PUT 请求
client.patch(path, json_data=None, **kw)PATCH 请求
client.delete(path, json_data=None, **kw)DELETE 请求
client.head(path, **kw)HEAD 请求
client.options(path, **kw)OPTIONS 请求

所有方法接受以下关键字参数:

ArgumentTypeDescription
body`str \bytes`
headersdict请求头
query_paramsdict查询字符串参数
form_datadict表单数据字段
filesdict文件上传(名称 → bytes)