DEV Community

Cover image for Rust API testen: Ein umfassender Leitfaden
Emre Demir
Emre Demir

Posted on • Originally published at apidog.com

Rust API testen: Ein umfassender Leitfaden

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.

Testen Sie Apidog noch heute

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:3000 an.
  • 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-cli gegen 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:

  1. 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.
  2. Mocks sind teilbar. Frontend-Teams erhalten eine URL mit realistischen Antworten, bevor der Handler fertig ist.
  3. OpenAPI entsteht aus realen Requests. Gespeicherte Anfragen können als OpenAPI 3.1 exportiert werden, ohne jede Route manuell mit utoipa oder aide zu 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();
}
Enter fullscreen mode Exit fullscreen mode

Starten Sie ihn lokal:

cargo run
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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");
});
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

Erstellen Sie in Apidog einen Request:

  • Methode: POST
  • URL: {{baseUrl}}/users
  • Body: JSON
{
  "name": "Ada Lovelace",
  "email": "ada@example.com"
}
Enter fullscreen mode Exit fullscreen mode

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(/^[^@]+@[^@]+$/);
});
Enter fullscreen mode Exit fullscreen mode

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")]
Enter fullscreen mode Exit fullscreen mode

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");
});
Enter fullscreen mode Exit fullscreen mode

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(),
    }))
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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: {}
Enter fullscreen mode Exit fullscreen mode

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: done gesendet?
  • 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:

  1. Rechtsklick auf den Request.
  2. Smart Mock aktivieren.
  3. Mock-URL an das Frontend geben.

Apidog liefert dann eine synthetische Antwort, z. B. unter:

https://mock.apidog.com/m1/<projectId>/users
Enter fullscreen mode Exit fullscreen mode

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()
};
Enter fullscreen mode Exit fullscreen mode

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:

  1. health-check
    • Erwartet 200
  2. create-user
    • Erwartet 200
    • Speichert body.id als Variable
  3. create-user-missing-email
    • Erwartet 422
  4. me
    • Nutzt JWT-Pre-Request
    • Erwartet 200
    • Prüft, ob die zurückgegebene id passt
  5. 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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 test prü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:

  1. Server lokal starten.
  2. baseUrl als Umgebung speichern.
  3. Health-Check anlegen.
  4. JSON-Requests und Fehlerfälle testen.
  5. Auth auf Ordnerebene automatisieren.
  6. Mocks für unfertige Handler bereitstellen.
  7. Szenario exportieren und in CI ausführen.
  8. 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)