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.
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ế:
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àorustc.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.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();
}
Trong Apidog:
- Tạo project mới.
- Mở phần quản lý Environment.
- Tạo environment tên
Rust Local. - 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
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à
3000không. - Server có bind vào
0.0.0.0:3000khô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));
Trong Apidog, tạo request:
- Method:
POST - URL:
{{baseUrl}}/users - Body: JSON
{
"name": "Ada Lovelace",
"email": "ada@example.com"
}
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(/^[^@]+@[^@]+$/);
});
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);
});
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(),
}))
}
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);
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: { ... }
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 {
// ...
}
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:
- Nhấp chuột phải vào request.
- Chọn Smart Mock.
- Bật mock.
Apidog sẽ tạo mock URL dạng:
https://mock.apidog.com/m1/<projectId>/users
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()
};
Khi handler Rust sẵn sàng, frontend chỉ cần đổi base URL về:
http://localhost:3000
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 Boot và quy 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:
-
health-check: assert200. -
create-user: assert200, lấybody.idvào biến. -
create-user-missing-email: assert422. -
me: dùng JWT, assert200vàidtrả về đúng. - 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
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"
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:
- Mở menu Export trong Apidog.
- Chọn OpenAPI 3.1.
- 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
và ghi ra:
openapi.json
Đ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)