Rust liefert Ihnen einen schnellen, typsicheren HTTP-Server in wenigen hundert Zeilen. Was Rust Ihnen nicht automatisch liefert, ist ein kurzer Feedback-Loop für API-Verträge: Statuscodes, JSON-Formen, Header, Authentifizierung und Fehlerfälle müssen gegen einen laufenden Server geprüft werden. Genau dafür lohnt sich ein Tool außerhalb der Rust-Toolchain, das HTTP spricht und nicht auf cargo test oder den nächsten vollständigen Build wartet.
Dieser Leitfaden zeigt einen vollständigen Rust-API-Test-Workflow mit Apidog: lokale Axum- oder Actix-Server anbinden, Requests speichern, Serde-JSON validieren, JWT-Authentifizierung automatisieren, unfertige Endpunkte mocken und die Sammlung als CI-Test ausführen. Ziel ist ein wiederverwendbares Projekt, das Vertragsänderungen erkennt, bevor sie in Produktion landen.
Wenn Sie bisher mit Postman oder curl arbeiten, bekommen Sie zusätzlich Design-First-Funktionen: eine OpenAPI-Spezifikation aus gespeicherten Requests, teilbare Mock-URLs und Team-Umgebungen. Die Postman-Migration ist ein eigenes Thema; hier geht es konkret um Rust.
TL;DR
- Starten Sie Ihre Rust-API lokal, z. B. mit
cargo run. - Legen Sie in Apidog eine Umgebung mit
baseUrl=http://localhost:3000an. - Speichern Sie zuerst einen einfachen
GET /healthz-Request als Smoke-Test. - Testen Sie JSON-Endpunkte mit realen Serde-Payloads und Response-Assertions.
- Speichern Sie JWTs als Umgebungsvariable und verwenden Sie Bearer Auth auf Ordnerebene.
- Mocken Sie unfertige Handler, damit Frontend-Teams parallel arbeiten können.
- Exportieren Sie das Testszenario und führen Sie es in CI mit
apidog-cligegen die laufende Rust-Binärdatei aus.
Warum Rust-APIs außerhalb der Rust-Toolchain testen?
cargo test bleibt wichtig, prüft aber primär Rust-Code gegen Rust-Typen. Ein HTTP-Vertrag besteht aus etwas anderem:
- Statuscodes
- JSON-Feldern
- Headern
- Authentifizierungsverhalten
- Fehlerantworten
- Timeouts
- Streaming-Verhalten
Für jeden Fall einen eigenen tower::ServiceExt::oneshot-Test zu schreiben, funktioniert, erzeugt aber schnell Wartungsaufwand. Zusätzlich braucht das Frontend oft denselben Vertrag als Mock.
Apidog legt diese Vertragsschicht neben den laufenden Server:
- Build und Vertrag sind entkoppelt. Sie testen die kompilierte API per HTTP, ohne bei jeder kleinen Änderung einen neuen Rust-Test schreiben zu müssen.
- Mocks sind teilbar. Frontend-Teams erhalten eine URL mit realistischen Antworten, bevor der Handler fertig ist.
-
OpenAPI entsteht aus realen Requests. Gespeicherte Anfragen können als OpenAPI 3.1 exportiert werden, ohne jede Route manuell mit
utoipaoderaidezu annotieren.
Schritt 1: Rust-Server lokal starten
Ein minimaler Axum-Server mit Health-Check:
use axum::{routing::get, Router};
use tokio::net::TcpListener;
#[tokio::main]
async fn main() {
let app = Router::new().route("/healthz", get(|| async { "ok" }));
let listener = TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
Starten Sie ihn lokal:
cargo run
Wichtig für lokale Tests: Binden Sie während der Entwicklung an 0.0.0.0:3000, nicht nur an 127.0.0.1:3000. Das vermeidet Probleme, wenn Apidog, Docker oder andere lokale Tools über eine andere Schnittstelle zugreifen.
Schritt 2: Apidog-Umgebung anlegen
Erstellen Sie in Apidog ein neues Projekt und legen Sie eine Umgebung Rust Local an.
| Variable | Wert |
|---|---|
baseUrl |
http://localhost:3000 |
token |
leer lassen |
apiVersion |
v1 |
Optional legen Sie zusätzlich Rust Staging an, z. B. mit Ihrer Staging-URL als baseUrl.
Der Vorteil: Alle Requests verwenden {{baseUrl}}. Der Wechsel zwischen lokalem Server und Staging erfolgt später per Dropdown statt per Suchen-und-Ersetzen.
Schritt 3: Ersten Smoke-Test speichern
Erstellen Sie in Apidog einen Ordner Rust API und darin einen Request:
- Methode:
GET - URL:
{{baseUrl}}/healthz
Senden Sie den Request. Erwartet:
200 OK
ok
Speichern Sie ihn als health-check.
Fügen Sie im Tab Tests eine einfache Assertion hinzu:
pm.test("Status is 200", () => {
pm.expect(pm.response.code).to.eql(200);
});
pm.test("Body is ok", () => {
pm.expect(pm.response.text()).to.eql("ok");
});
Wenn Sie Connection refused erhalten:
- Läuft der Server?
- Stimmt der Port?
- Bindet der Server an
0.0.0.0:3000? - Verwendet Apidog die richtige Umgebung?
Schritt 4: JSON-Endpunkt mit Serde testen
Die typische Rust-API verarbeitet JSON über Serde. Beispiel für POST /users:
use axum::{extract::Json, routing::post, Router};
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct CreateUser {
name: String,
email: String,
}
#[derive(Serialize)]
struct User {
id: u64,
name: String,
email: String,
}
async fn create_user(Json(payload): Json<CreateUser>) -> Json<User> {
Json(User {
id: 1,
name: payload.name,
email: payload.email,
})
}
let app = Router::new().route("/users", post(create_user));
Erstellen Sie in Apidog einen Request:
- Methode:
POST - URL:
{{baseUrl}}/users - Body: JSON
{
"name": "Ada Lovelace",
"email": "ada@example.com"
}
Speichern Sie den Request als create-user.
Fügen Sie Tests hinzu:
pm.test("Status is 200", () => {
pm.expect(pm.response.code).to.eql(200);
});
pm.test("Body has id, name and email", () => {
const body = pm.response.json();
pm.expect(body).to.have.property("id");
pm.expect(body.name).to.eql("Ada Lovelace");
pm.expect(body.email).to.match(/^[^@]+@[^@]+$/);
});
Damit prüfen Sie nicht nur, dass Rust intern kompiliert, sondern dass die HTTP-Antwort weiterhin den erwarteten Vertrag erfüllt.
Wenn später jemand Serde-Attribute wie dieses ergänzt:
#[serde(rename_all = "camelCase")]
und sich dadurch Feldnamen ändern, schlägt der gespeicherte Apidog-Test fehl.
Schritt 5: Serde-Fehlerfälle abdecken
Testen Sie nicht nur den erfolgreichen Pfad. Gerade bei Serde ist wichtig, wie fehlerhafte Payloads behandelt werden.
Legen Sie drei weitere Requests an:
| Request | Body | Erwartung |
|---|---|---|
create-user-missing-email |
{ "name": "Ada" } |
422, Body erwähnt missing field email
|
create-user-extra-field |
{ "name": "Ada", "email": "a@b.c", "admin": true } |
200, wenn #[serde(deny_unknown_fields)] nicht aktiv ist; sonst 422
|
create-user-wrong-type |
{ "name": 1, "email": "a@b.c" } |
422, Body erwähnt invalid type: integer
|
Beispiel-Test für den fehlenden E-Mail-Fall:
pm.test("Missing email returns 422", () => {
pm.expect(pm.response.code).to.eql(422);
});
pm.test("Error mentions missing email", () => {
pm.expect(pm.response.text()).to.include("email");
});
So dokumentieren Sie Ihre tatsächliche Validierungsrichtlinie. Wenn Sie später deny_unknown_fields aktivieren, wird der entsprechende Test rot und zeigt eine öffentliche Vertragsänderung an.
Schritt 6: JWT-geschützte Routen testen
Viele Rust-APIs schützen produktive Endpunkte per Middleware oder Extractor. Beispielhaft kann ein Handler einen Token aus einem Cookie lesen und Claims decodieren:
use axum::{extract::Json, http::StatusCode};
use axum_extra::extract::cookie::PrivateCookieJar;
use jsonwebtoken::{decode, DecodingKey, Validation};
async fn me(jar: PrivateCookieJar) -> Result<Json<User>, StatusCode> {
let token = jar.get("token").ok_or(StatusCode::UNAUTHORIZED)?;
let claims = decode::<Claims>(
token.value(),
&DecodingKey::from_secret(b"secret"),
&Validation::default(),
)
.map_err(|_| StatusCode::UNAUTHORIZED)?;
Ok(Json(User {
id: claims.claims.sub,
name: "Ada".into(),
email: "ada@example.com".into(),
}))
}
In Apidog können Sie ein JWT vor jedem Request automatisch erzeugen. Legen Sie auf Ordnerebene ein Pre-Request-Skript an:
const jwt = require("jsonwebtoken");
const token = jwt.sign(
{
sub: 1,
exp: Math.floor(Date.now() / 1000) + 3600
},
"secret"
);
pm.environment.set("token", token);
Setzen Sie in den Ordnereinstellungen:
- Auth:
Bearer Token - Token:
{{token}}
Alle Requests im Ordner erben nun die Authentifizierung. Dadurch vermeiden Sie Tests, die nur wegen abgelaufener Tokens fehlschlagen.
Für weitere Auth-Assertions siehe auch: wie man die JWT-Authentifizierung in APIs testet.
Schritt 7: Streaming und Server-Sent Events testen
Rust-Webframeworks unterstützen Streaming sehr gut. Bei Axum liefert Sse typischerweise text/event-stream-Chunks aus:
data: { ... }
data: { ... }
event: done
data: {}
In Apidog erstellen Sie dafür einen normalen GET-Request auf den SSE-Endpunkt. Wenn der Response-Header Content-Type: text/event-stream lautet, sehen Sie die Frames im Streaming-Panel, sobald sie eintreffen.
Prüfen Sie insbesondere:
- Kommt der erste Chunk innerhalb Ihrer erwarteten Latenz?
- Wird ein erwartetes Event wie
event: donegesendet? - Schließt der Stream innerhalb eines definierten Zeitfensters?
- Hängt der Handler versehentlich endlos?
Wenn der Endpunkt WebSockets statt SSE verwendet, nutzen Sie in Apidog den separaten WebSocket-Request-Typ. Das Muster bleibt gleich: Verbindung herstellen, Nachrichtenfolge speichern, Antworten validieren.
Schritt 8: Unfertige Rust-Handler mocken
Frontend-Arbeit sollte nicht blockieren, nur weil ein Rust-Handler noch nicht fertig ist. Apidog-Mocks geben eine stabile URL zurück, die denselben Vertrag erfüllt wie der geplante Endpunkt.
Für den gespeicherten Request create-user:
- Rechtsklick auf den Request.
- Smart Mock aktivieren.
- Mock-URL an das Frontend geben.
Apidog liefert dann eine synthetische Antwort, z. B. unter:
https://mock.apidog.com/m1/<projectId>/users
Der Body entspricht dem gespeicherten Beispiel.
Für dynamischere Antworten verwenden Sie Advanced Mock:
return {
id: Math.floor(Math.random() * 10000),
name: body.name,
email: body.email,
createdAt: new Date().toISOString()
};
Das Frontend kann gegen diesen Mock entwickeln. Sobald der echte Rust-Handler bereit ist, wird nur die Basis-URL wieder auf http://localhost:3000 geändert.
Dasselbe Prinzip wird auch in anderen API-Workflows genutzt, z. B. beim Erstellen und Testen einer Spring Boot API oder im allgemeinen API-Test-Workflow.
Schritt 9: Testszenario für CI erstellen
Apidog-Testszenarien verketten Requests, teilen Variablen und laufen headless.
Ein sinnvolles Szenario:
-
health-check- Erwartet
200
- Erwartet
-
create-user- Erwartet
200 - Speichert
body.idals Variable
- Erwartet
-
create-user-missing-email- Erwartet
422
- Erwartet
-
me- Nutzt JWT-Pre-Request
- Erwartet
200 - Prüft, ob die zurückgegebene
idpasst
- SSE-Request
- Erwartet, dass der Stream innerhalb von 5 Sekunden abgeschlossen wird
Exportieren Sie das Szenario als JSON und committen Sie es z. B. nach:
tests/apidog/contract.json
Beispiel für CI:
- name: Run API contract tests
run: |
cargo build --release
./target/release/myserver &
sleep 2
apidog-cli run tests/apidog/contract.json --env "Rust Local"
Damit testet jeder Pull Request die reale HTTP-Oberfläche der kompilierten Rust-Binärdatei. Wenn ein Handler plötzlich einen anderen Statuscode, ein umbenanntes Feld oder eine geänderte Auth-Logik ausliefert, schlägt CI fehl.
Schritt 10: OpenAPI aus gespeicherten Requests exportieren
Wenn die Requests stabil sind, exportieren Sie aus Apidog eine OpenAPI-3.1-Spezifikation.
Damit erhalten Consumer Ihrer API einen Vertrag, der auf real getesteten Requests basiert. Das ist besonders nützlich für generierte Clients in:
- TypeScript
- Swift
- Kotlin
- Python
- anderen Rust-Crates
Wenn Sie die Spezifikation versionieren möchten, exportieren Sie sie in CI:
apidog-cli export --format openapi --output openapi.json
Committen Sie openapi.json ins Repository oder veröffentlichen Sie es als CI-Artefakt.
FAQ
Funktioniert Apidog mit Axum und Actix-web?
Ja. Apidog spricht HTTP, nicht Rust. Axum, Actix-web, Rocket, Warp, Poem oder Loco funktionieren gleich, solange der Server erreichbar ist.
Wichtig für lokale Tests: Verwenden Sie bei Bedarf 0.0.0.0 statt 127.0.0.1.
Wie teste ich Handler, die paniken?
Nutzen Sie z. B. tower-http mit CatchPanicLayer, um Panics in 500-Antworten umzuwandeln. Danach erstellen Sie in Apidog einen Request, der den Panic-Pfad auslöst, und prüfen den Statuscode.
Ohne Panic-Catching bricht die Verbindung ab. Auch das kann ein gültiger Vertragstest sein, wenn dieses Verhalten bewusst so bleibt.
Kann ich Apidog gegen eine Rust-Binärdatei in Docker ausführen?
Ja. Setzen Sie baseUrl auf den gemappten Host-Port des Containers.
Bei Docker Compose gibt es zwei übliche Optionen:
- Apidog-Runner ins gleiche Docker-Netzwerk hängen
- den gemappten Port des Hosts verwenden
Was ist mit gRPC?
Apidog unterstützt gRPC-Requests. Importieren Sie Ihre .proto-Dateien, wählen Sie Service und Methode aus, füllen Sie die Payload und senden Sie den Request. Umgebungen, Authentifizierung und Testszenarien funktionieren ähnlich wie bei REST.
Ersetzt Apidog cargo test?
Nein. Behalten Sie Unit-Tests in Rust bei.
-
cargo testprüft interne Logik. - Apidog prüft den laufenden HTTP-Vertrag.
Sie wollen beides: Rust-Tests für Funktionen und Apidog-Tests für Statuscodes, Header, Auth, JSON-Formen, Fehlerantworten und Streaming-Verhalten.
Ist Apidog kostenlos für Rust-Open-Source-Projekte?
Ja. Der Apidog-Client ist für Einzelpersonen und kleine Teams kostenlos. Testszenarien, Mocks und OpenAPI-Export sind Teil des kostenlosen Tarifs. Für öffentliche Rust-APIs können Sie die Projektdatei oder exportierte Szenarien ins Repository legen, damit Contributors dieselben Tests ausführen können.
Zusammenfassung
Rust-APIs brauchen einen Feedback-Loop, der nicht nur vom Compiler abhängt. Mit Apidog testen Sie die reale HTTP-Oberfläche Ihrer Axum- oder Actix-API: Requests, JSON-Verträge, JWTs, Fehlerfälle, Streaming und Mocks.
Der praktische Ablauf:
- Server lokal starten.
-
baseUrlals Umgebung speichern. - Health-Check anlegen.
- JSON-Requests und Fehlerfälle testen.
- Auth auf Ordnerebene automatisieren.
- Mocks für unfertige Handler bereitstellen.
- Szenario exportieren und in CI ausführen.
- OpenAPI aus den gespeicherten Requests generieren.
Laden Sie Apidog herunter und richten Sie es auf Ihren Rust-Server. Die Einrichtung dauert nur wenige Minuten und gibt Ihrem Team einen überprüfbaren API-Vertrag außerhalb von cargo.
Top comments (0)