Pruebas
Las pruebas rápidas y fiables necesitan controlar tu aplicación igual que lo hace
un cliente: sin arrancar un servidor ni tocar la red. El TestClient de
Rustango ejecuta tu router en proceso: llamas a client.get("/path"), la
petición se enruta a través de la pila real (extractores, middleware, manejadores)
y te devuelve la respuesta para hacer aserciones. Añade el aislamiento por
reversión de transacción para las pruebas de base de datos y un conjunto de
aserciones de respuesta, y tienes el cliente de pruebas de Django + TestCase, en
Rust.
¿Hay algún término nuevo para ti aquí? router, handler, fixture, rollback — consulta el glosario.
Fuente:
rustango::test_client(TestClient,TestResponse),rustango::test_assertions(assert_status_2xx,assert_redirects,assert_cookie_set, …), yrustango::test_db(with_rollback) — siempre compilados.Versión ejecutable: los fragmentos de abajo son una prueba que pasa —
testing_doc.rs(cargo test -p rustango --test testing_doc). Casi cualquier otro*_doc.rsde este repositorio usaTestClientde la misma manera.
Tabla de contenidos
- Paso 1 — Controla tu aplicación con TestClient
- Paso 2 — Haz aserciones sobre la respuesta
- Enviar JSON, cabeceras y cuerpos
- Probar una API real
- Pruebas de base de datos con reversión
- Ayudantes de aserción de respuesta
- Véase también
Paso 1 — Controla tu aplicación con TestClient
Envuelve cualquier axum::Router en un TestClient y envía peticiones — no se
enlaza ningún socket, no se genera ninguna tarea de servidor. La petición fluye a
través de tu middleware y tus manejadores reales:
use rustango::test_client::TestClient;
let client = TestClient::new(app()); // app() returns your Router
let res = client.get("/ping").send().await; // routed in-process
assert_eq!(res.status, 200);
TestClient tiene get / post / put / patch / delete / head, cada uno
devolviendo un constructor que finalizas con .send().await.
Paso 2 — Haz aserciones sobre la respuesta
TestResponse expone el estado y el cuerpo en la forma que necesites:
let res = client.get("/ping").send().await;
res.status; // u16 — e.g. 200
res.text(); // body as a String
res.header("content-type"); // Option<&str>
// JSON, two ways:
let res = client.post("/echo").json(&json!({ "name": "Ada" })).send().await;
assert_eq!(res.json_value()["name"], "Ada"); // untyped
#[derive(serde::Deserialize)]
struct Out { name: String }
let out: Out = res.json(); // typed
assert_eq!(out.name, "Ada");
Enviar JSON, cabeceras y cuerpos
El constructor de peticiones encadena todo antes de .send():
let res = client
.post("/api/posts")
.header("authorization", "Bearer <token>") // auth, content negotiation, …
.json(&json!({ "title": "Hello", "body": "..." }))
.send()
.await;
assert_eq!(res.status, 201);
Usa .body(...) para cuerpos sin procesar (que no sean JSON), y una ruta
inexistente devuelve un 404 real — verificado en la prueba de respaldo.
Probar una API real
app() en tus pruebas no es más que tu router. Para una API respaldada por base de
datos, constrúyela exactamente como lo hace main.rs pero con un pool de pruebas —
el patrón que usa la mayoría de las pruebas *_doc.rs:
async fn app() -> axum::Router {
let pool = test_pool().await; // a sqlite::memory: or test DB pool
PostViewSet::router("/api/posts", pool)
}
#[tokio::test]
async fn create_then_list() {
let client = TestClient::new(app().await);
let created = client.post("/api/posts")
.json(&json!({ "title": "Hi", "body": "b" }))
.send().await;
assert_eq!(created.status, 201);
let list = client.get("/api/posts").send().await;
assert!(list.json_value()["results"].is_array());
}
Esta es la prueba de ViewSets de aquella guía — el mismo TestClient.
Pruebas de base de datos con reversión
Las pruebas que escriben en una base de datos no deben filtrar estado entre sí.
test_db::with_rollback ejecuta tu prueba dentro de una transacción y la revierte
al final, de modo que cada prueba parte del mismo estado limpio y nada persiste:
use rustango::test_db::with_rollback;
#[tokio::test]
async fn creating_a_post_persists_it() {
with_rollback(&pool, |tx| async move {
// ... insert + assert against `tx` ...
// everything here is rolled back when the closure returns
}).await;
}
Para SQLite, las pruebas *_sqlite_live.rs repartidas por este repositorio usan en
su lugar una base de datos en memoria por prueba — también totalmente aislada, con
cero configuración externa.
Ayudantes de aserción de respuesta
Para valores axum::Response sin procesar (por ejemplo, de tower::oneshot),
test_assertions se lee como los assertContains / assertRedirects de Django:
use rustango::test_assertions::{assert_status_2xx, assert_redirects, assert_cookie_set};
assert_status_2xx(&res);
assert_redirects(&res, "/login?next=/dashboard");
assert_cookie_set(&res, "rustango_session", None);
También disponibles: assert_status / assert_status_in / assert_status_4xx /
assert_status_5xx, assert_header, assert_content_type,
assert_redirect_chain, assert_cookie_not_set, y assert_messages.
Véase también
- ViewSets · Vistas HTML — a qué apuntas el
TestClient. - Middleware —
TestClienttambién ejercita las capas (sin base de datos, mediantetower::oneshotenmiddleware.rs). - Primeros pasos — el Paso 16 escribe la primera prueba.
- CLI
manage—make:testgenera un módulo de pruebas.
