DEV Community

Cover image for Kiểm Tra API Rust: Hướng Dẫn Chi Tiết
Sebastian Petrus
Sebastian Petrus

Posted on • Originally published at apidog.com

Kiểm Tra API Rust: Hướng Dẫn Chi Tiết

Rust giúp bạn dựng một HTTP server nhanh và an toàn kiểu dữ liệu chỉ với vài trăm dòng code. Nhưng vòng lặp phản hồi để kiểm thử API thường chậm: build lâu, cargo test chạy lại nhiều phần khi trait thay đổi, và mỗi endpoint lại cần một integration test riêng trước khi bạn gọi thử nó bằng HTTP thực.

Dùng thử Apidog hôm nay

Bài viết này hướng dẫn cách kiểm thử API Rust bằng Apidog: cấu hình server Axum hoặc Actix, tạo request cho từng endpoint, xác thực JSON do Serde serialize, xử lý JWT, mock endpoint cho frontend, rồi đóng gói thành kịch bản kiểm thử chạy trong CI. Kết quả là một project Apidog có thể tái sử dụng để phát hiện lỗi lệch hợp đồng trước khi cargo build --release hoàn tất.

Nếu bạn đang dùng Postman hoặc curl, Apidog cũng cung cấp workflow design-first: OpenAPI sinh từ request đã lưu, mock URL có thể chia sẻ và môi trường dùng chung cho team. Phần di chuyển từ Postman có thể đọc riêng tại câu chuyện di chuyển từ Postman; bài này tập trung vào Rust.

Tóm tắt quy trình

  • Chạy API Rust cục bộ bằng cargo run, ví dụ Axum hoặc Actix-web.
  • Tạo environment trong Apidog với baseUrl = http://localhost:3000.
  • Gửi request đầu tiên tới GET /healthz để xác nhận server và environment hoạt động.
  • Với endpoint JSON, dùng payload khớp struct Serde và thêm assertion trong tab Tests.
  • Với route cần xác thực, tạo JWT trong Pre-Request Script, lưu vào {{token}}, rồi áp dụng Bearer Auth ở cấp folder.
  • Mock các handler chưa hoàn thiện để frontend có thể phát triển song song.
  • Gom request thành Test Scenario và chạy trong CI bằng apidog-cli.

Tại sao nên kiểm thử API Rust bên ngoài cargo test

cargo test phù hợp cho unit test và integration test ở mức code. Nhưng khi cần kiểm tra hợp đồng HTTP thực tế — status code, JSON shape, header, auth, lỗi validation — bạn thường phải viết thêm test bằng tower::ServiceExt::oneshot hoặc client test riêng cho từng case.

Apidog hoạt động ở lớp HTTP, trên server đang chạy. Một request được lưu một lần, assertion nằm cạnh request, frontend và backend cùng xem một hợp đồng. Khi một thay đổi Serde như #[serde(rename_all = "camelCase")] làm đổi response từ user_id sang userId, test trong Apidog sẽ báo lỗi trước khi thay đổi đi vào production.

Ba lợi ích thực tế:

  1. Contract test tách khỏi build Rust

    Apidog kiểm thử binary đang chạy, không phụ thuộc trực tiếp vào rustc.

  2. Mock có thể chia sẻ

    Frontend nhận một URL trả JSON đúng hợp đồng thay vì chờ handler backend hoàn thiện.

  3. OpenAPI sinh từ request thực tế

    Apidog có thể xuất OpenAPI 3.1 từ request đã lưu, hữu ích cho client TypeScript, Swift, Kotlin hoặc Python mà không cần viết tay toàn bộ file .yaml.

Bước 1: Thêm server Rust làm environment trong Apidog

Khởi động API Rust của bạn. Ví dụ tối thiểu với Axum:

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

Trong Apidog:

  1. Tạo project mới.
  2. Mở phần quản lý Environment.
  3. Tạo environment tên Rust Local.
  4. Thêm các biến sau:
Biến Giá trị
baseUrl http://localhost:3000
token để trống hiện tại
apiVersion v1

Tạo thêm environment Rust Staging với URL staging của bạn. Khi request dùng {{baseUrl}}, bạn có thể đổi môi trường từ local sang staging mà không cần sửa từng request.

Bước 2: Gửi request đầu tiên tới /healthz

Tạo folder Rust API, sau đó tạo request mới:

  • Method: GET
  • URL: {{baseUrl}}/healthz

Nhấn Send. Nếu server đang chạy, response sẽ là:

ok
Enter fullscreen mode Exit fullscreen mode

với status code 200.

Lưu request với tên health-check.

Nếu bị lỗi connection refused, kiểm tra:

  • Server có đang chạy không.
  • Port có đúng là 3000 không.
  • Server có bind vào 0.0.0.0:3000 không.

Trong môi trường local, bind vào 0.0.0.0 giúp Apidog và Docker container truy cập ổn định hơn so với chỉ bind 127.0.0.1.

Bước 3: Kiểm thử JSON request/response với Serde

Ví dụ endpoint 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

Trong Apidog, tạo request:

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

Gửi request và lưu với tên create-user.

Sau đó mở tab Tests và thêm assertion:

pm.test("Status is 200", () => {
  pm.expect(pm.response.code).to.eql(200);
});

pm.test("Body has id, name, 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

Các assertion này kiểm tra hợp đồng HTTP thực tế thay vì chỉ kiểm tra type trong Rust. Nếu response shape thay đổi, test sẽ fail ngay.

Bước 4: Kiểm thử lỗi validation từ Serde

Serde và framework web Rust có hành vi cụ thể khi input sai schema. Hãy tạo các request cố ý sai để ghi lại hợp đồng lỗi.

Request Body Kỳ vọng
create-user-missing-email { "name": "Ada" } 422, body đề cập missing field email
create-user-extra-field { "name": "Ada", "email": "a@b.c", "admin": true } 200 nếu chưa dùng #[serde(deny_unknown_fields)]; 422 nếu có
create-user-wrong-type { "name": 1, "email": "a@b.c" } 422, đề cập invalid type: integer

Ví dụ test cho case thiếu email:

pm.test("Missing email returns 422", () => {
  pm.expect(pm.response.code).to.eql(422);
});
Enter fullscreen mode Exit fullscreen mode

Nếu sau này bạn bật #[serde(deny_unknown_fields)], request create-user-extra-field sẽ fail và báo rằng contract public đã thay đổi.

Bước 5: Kiểm thử route được bảo vệ bằng JWT

Nhiều API Rust production dùng middleware hoặc extractor để xác thực JWT. Ví dụ với Axum:

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

Trong Apidog, tạo JWT tự động bằng Pre-Request Script ở cấp folder:

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

Sau đó mở folder settings:

  • Auth type: Bearer Token
  • Token: {{token}}

Mọi request trong folder sẽ kế thừa JWT mới khi chạy. Cách này tránh lỗi token hết hạn trong test.

Xem thêm: cách kiểm thử xác thực JWT trong API.

Bước 6: Kiểm thử streaming và Server-Sent Events

Các framework web Rust hỗ trợ streaming tốt. Với Axum, Sse có thể wrap một futures::Stream và trả về Content-Type: text/event-stream.

Định dạng SSE thường là:

data: { ... }

Enter fullscreen mode Exit fullscreen mode

Mỗi frame được gửi qua connection đang mở. Trong Apidog, request vẫn là GET, nhưng response panel sẽ chuyển sang chế độ streaming khi nhận text/event-stream.

Các điểm nên kiểm tra:

  • Chunk đầu tiên đến trong SLA mong muốn.
  • Có event cụ thể trước khi connection đóng, ví dụ event: done.
  • Tổng thời gian stream không vượt ngưỡng, ví dụ 5 giây.

Nếu handler bị quên thoát khỏi vòng lặp như:

while let Some(event) = stream.next().await {
    // ...
}
Enter fullscreen mode Exit fullscreen mode

stream có thể chạy mãi. Đặt timeout trong request để test fail thay vì treo.

Nếu endpoint dùng WebSocket thay vì SSE, dùng request type WebSocket trong Apidog: tạo connection, lưu message sequence và assert response tương ứng.

Bước 7: Mock API Rust để frontend phát triển song song

Frontend thường bị chặn bởi handler chưa có, không phải bởi compiler Rust. Mock trong Apidog cho phép bạn công bố URL ổn định trả response đúng hợp đồng trước khi backend hoàn thiện.

Với request create-user:

  1. Nhấp chuột phải vào request.
  2. Chọn Smart Mock.
  3. Bật mock.

Apidog sẽ tạo mock URL dạng:

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

Mock response khớp với example đã lưu. Frontend có thể POST tới URL này như gọi server Rust thật.

Với mock động, dùng 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

Khi handler Rust sẵn sàng, frontend chỉ cần đổi base URL về:

http://localhost:3000
Enter fullscreen mode Exit fullscreen mode

Logic gọi API không cần đổi.

Các workflow tương tự cũng được áp dụng trong xây dựng và kiểm thử API Spring Bootquy trình kiểm thử API nói chung.

Bước 8: Lưu thành Test Scenario để chạy CI

Apidog Test Scenarios cho phép xâu chuỗi nhiều request, chia sẻ biến và chạy headless.

Một scenario contract cơ bản có thể gồm:

  1. health-check: assert 200.
  2. create-user: assert 200, lấy body.id vào biến.
  3. create-user-missing-email: assert 422.
  4. me: dùng JWT, assert 200id trả về đúng.
  5. SSE request: assert stream hoàn tất trong vòng 5 giây.

Xuất scenario thành JSON, commit vào repository:

tests/apidog/contract.json
Enter fullscreen mode Exit fullscreen mode

Ví dụ chạy trong 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

Từ đây, mọi PR thay đổi handler sẽ được kiểm tra với binary Rust đang chạy. Nếu đổi tên field Serde, đổi status code hoặc làm sai JWT flow, CI sẽ fail trước khi merge.

Bước 9: Tạo OpenAPI từ request đã lưu

Khi bộ request đã ổn định:

  1. Mở menu Export trong Apidog.
  2. Chọn OpenAPI 3.1.
  3. Xuất file đặc tả.

File OpenAPI sẽ chứa các request đã lưu và example body bạn đã gửi. Bạn có thể dùng nó để sinh client có type cho TypeScript, Swift, Kotlin hoặc Python.

Nếu muốn lưu đặc tả trong repo Rust, chạy export từ CI:

apidog-cli export
Enter fullscreen mode Exit fullscreen mode

và ghi ra:

openapi.json
Enter fullscreen mode Exit fullscreen mode

Điều này không thay đổi cargo build, nhưng giúp người dùng API có contract cập nhật trên đĩa.

Câu hỏi thường gặp

Apidog có hoạt động với Axum và Actix-web không?

Có. Apidog giao tiếp bằng HTTP, không phụ thuộc framework Rust. Axum, Actix-web, Rocket, Warp, Poem hoặc Loco đều hoạt động nếu server trả HTTP response.

Lưu ý chính khi test local: bind server vào 0.0.0.0 thay vì chỉ 127.0.0.1.

Làm sao kiểm thử handler gây panic?

Đặt CatchPanicLayer của tower-http trước router. Panic sẽ được chuyển thành status 500 với response body phù hợp. Sau đó tạo request trong Apidog để kích hoạt path đó và assert 500.

Nếu không catch panic, connection có thể bị đóng và Apidog báo network error. Đây cũng là tín hiệu contract bị phá vỡ.

Có thể chạy Apidog với Rust binary trong Docker không?

Có. Trỏ baseUrl tới port đã expose của container.

Nếu server chạy trong Docker Compose, đảm bảo runner của Apidog nằm cùng network hoặc dùng mapped port trên host.

Còn gRPC thì sao?

Apidog có request type cho gRPC. Bạn có thể import file .proto, chọn service và method, nhập payload rồi gửi request. Environment, auth và test scenario vẫn dùng cùng mô hình như REST.

Test Scenario có thay thế cargo test không?

Không. cargo test vẫn nên dùng cho unit test và logic nội bộ Rust.

Apidog kiểm thử bề mặt HTTP đang chạy: response shape, status code, header, CORS, auth, validation error. Hai lớp test này bắt lỗi khác nhau và nên được dùng cùng nhau.

Apidog có miễn phí cho dự án mã nguồn mở Rust không?

Có. Apidog client miễn phí cho cá nhân và nhóm nhỏ. Test scenario, mock và export OpenAPI có trong gói miễn phí. Với API Rust public, bạn có thể đưa project file hoặc scenario vào repo để contributor clone về là có bộ test hợp đồng.

Tổng kết

API Rust cần một vòng lặp phản hồi nhanh hơn việc chờ compiler cho mọi thay đổi. Một collection request trong Apidog cung cấp lớp kiểm thử HTTP thực: request thật, assertion thật, mock cho frontend và scenario chạy trong CI với binary đang hoạt động.

Hãy bắt đầu bằng GET /healthz, thêm các endpoint JSON, phủ các case lỗi Serde, cấu hình JWT, rồi đưa toàn bộ vào CI. Khi contract thay đổi, test sẽ báo ngay thay vì để lỗi xuất hiện ở runtime.

Tải Apidog và trỏ nó vào server Rust của bạn. Thiết lập ban đầu chỉ mất vài phút, đổi lại bạn có một hợp đồng API tách khỏi cargo và dễ chia sẻ với frontend.

Top comments (0)