Test dell'API
Test di integrazione
Test dell'API è una lezione Learn Rust Coding gratuita su CoddyKit. Questa è la lezione 4 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Learn Rust Coding, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Learn Rust Coding include 4 lezioni in totale.
Perché testare un'API?
I test vi danno la certezza che gli endpoint si comportino correttamente e continuino a funzionare mentre modificate il codice. Per un'API REST, i test più utili sono i test di integrazione: esercitano il router reale dall'inizio alla fine, inviando richieste e verificando le risposte.
Questa lezione tratta i test unitari, il trucco oneshot di Tower e i test di integrazione completi.
Test unitari per la logica pura
La logica che non accede alla rete, come la convalida, può essere verificata con normali test unitari Rust. Inseriteli in un modulo #[cfg(test)] accanto al codice. Questi test vengono eseguiti rapidamente con cargo test.
fn validate_title(title: &str) -> bool {
!title.trim().is_empty()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rejects_empty() {
assert!(!validate_title(" "));
assert!(validate_title("buy milk"));
}
}Test asincroni
Gli handler sono asincroni, quindi le funzioni di test che usano await devono essere eseguite su un runtime. Usate #[tokio::test] invece di #[test]. Questo avvia un runtime Tokio per il test, consentendovi di chiamare codice asincrono con .await.
async fn add(a: i32, b: i32) -> i32 { a + b }
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn adds() {
assert_eq!(add(2, 3).await, 5);
}
}Testare senza una rete
I router Axum implementano il trait Service di Tower, quindi potete passar loro direttamente le richieste senza associare una porta. Il metodo oneshot accetta una singola Request e restituisce la Response. In questo modo i test di integrazione sono rapidi e deterministici.
// dev-dependencies needed: tower (for ServiceExt), http-body-util
use axum::{Router, routing::get};
use axum::http::{Request, StatusCode};
use axum::body::Body;
use tower::ServiceExt; // brings in oneshot
async fn check() {
let app = Router::new().route("/health", get(|| async { "ok" }));
let res = app
.oneshot(Request::builder().uri("/health").body(Body::empty()).unwrap())
.await.unwrap();
assert_eq!(res.status(), StatusCode::OK);
}Verificare il codice di stato
La prima cosa che la maggior parte dei test verifica è lo stato HTTP. Una risorsa mancante dovrebbe restituire 404, una creazione riuscita 201 e un body non valido 400. Create una richiesta per la route e confrontate res.status() con il codice previsto.
use axum::http::{Request, StatusCode};
use axum::body::Body;
use tower::ServiceExt;
async fn missing_returns_404(app: axum::Router) {
let res = app
.oneshot(Request::builder()
.uri("/todos/999")
.body(Body::empty()).unwrap())
.await.unwrap();
assert_eq!(res.status(), StatusCode::NOT_FOUND);
}Lettura del corpo della risposta
Per verificare il JSON, raccolga il corpo della risposta in byte e lo deserializzi. L'helper http_body_util::BodyExt::collect raccoglie il corpo, quindi serde_json lo analizza trasformandolo nel modello per consentire verifiche a livello di campo.
use http_body_util::BodyExt;
async fn read_json(res: axum::http::Response<axum::body::Body>) {
let bytes = res.into_body().collect().await.unwrap().to_bytes();
let todo: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
assert_eq!(todo["done"], false);
}Invio di un corpo JSON
Per testare una richiesta POST, costruisca una richiesta con un corpo JSON e l'header content-type corretto. Serializzi la struct di input, imposti content-type: application/json e la passi a oneshot.
use axum::http::{Request, StatusCode, header};
use axum::body::Body;
use tower::ServiceExt;
async fn create_returns_201(app: axum::Router) {
let body = serde_json::json!({ "title": "test" }).to_string();
let res = app
.oneshot(Request::builder()
.method("POST").uri("/todos")
.header(header::CONTENT_TYPE, "application/json")
.body(Body::from(body)).unwrap())
.await.unwrap();
assert_eq!(res.status(), StatusCode::CREATED);
}Un builder riutilizzabile per l'app di test
Ogni test dovrebbe iniziare da uno stato pulito. Scriva un helper che costruisca un router nuovo con un nuovo database in memoria o di test. Chiamandolo in ogni test, li mantiene isolati, impedendo che un test influenzi un altro.
use axum::Router;
use std::sync::{Arc, Mutex};
fn test_app() -> Router {
let store = Arc::new(Mutex::new(Vec::new()));
build(store) // same build() the real server uses
}La directory tests
I test di integrazione si trovano in una cartella tests/ al livello principale. Ogni file al suo interno viene compilato come crate separato che usa l'API pubblica della libreria. In questo modo è necessario testare attraverso la stessa interfaccia che vedono gli utenti reali.
tests/api.rscontiene i test degli endpoint.- Li esegua tutti con
cargo test.
// tests/api.rs
use my_api::build_router; // exported from lib.rs
#[tokio::test]
async fn health_ok() {
let _app = build_router();
// send a request and assert ...
}Test con un database
Quando gli handler usano sqlx, anche i test hanno bisogno di un database. Strategie comuni: un database di test dedicato, transazioni annullate dopo ogni test oppure la macro #[sqlx::test] di sqlx, che prepara automaticamente un database pulito per ogni test.
// requires sqlx test features and a DATABASE_URL
use sqlx::PgPool;
#[sqlx::test]
async fn inserts_todo(pool: PgPool) {
let todo = create(&pool, "learn rust").await.unwrap();
assert_eq!(todo.title, "learn rust");
assert_eq!(todo.done, false);
}Cosa testare
Punti a una suite equilibrata:
- Percorso principale: le richieste valide restituiscono lo status e il corpo corretti.
- Errori: le risorse mancanti restituiscono 404, gli input non validi restituiscono 400.
- Casi limite: liste vuote, valori ai limiti, duplicati.
Esegua cargo test in CI, così le regressioni vengono rilevate prima del deployment.
Verifica rapida
Verifichi la Sua comprensione dei test dell'API.
Riepilogo
Ha imparato a testare una REST API in Rust:
- Testi con unit test la logica pura; usi
#[tokio::test]per il codice asincrono. oneshotesegue direttamente il router, senza bisogno di una porta.- Costruisca richieste con corpi e header; raccolga e analizzi i corpi delle risposte.
- Usi un builder di app nuovo per ogni test, per garantire l'isolamento; inserisca i test di integrazione in
tests/. #[sqlx::test]prepara database puliti; copra percorsi principali, errori e casi limite.
Domande Frequenti
La lezione «Test dell'API» è gratuita?
Sì — il testo completo di «Test dell'API» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Learn Rust Coding, passa a CoddyKit PRO. Il corso Learn Rust Coding include 4 lezioni in totale.
Cosa imparerò in «Test dell'API»?
Test di integrazione Eserciti Learn Rust Coding con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.
Ho bisogno di esperienza per iniziare Learn Rust Coding?
Non è richiesta alcuna esperienza precedente. Learn Rust Coding su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 4 di 4.
Quanto tempo richiede la lezione «Test dell'API»?
La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.
Posso scrivere ed eseguire codice in questa lezione Learn Rust Coding?
Sì. Ogni lezione Learn Rust Coding include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.