วิธีทดสอบ Rust API

INEZA Felin-Michel

INEZA Felin-Michel

13 May 2026

วิธีทดสอบ Rust API

Apidog สำหรับองค์กร

การติดตั้งแบบ On-Premises

SSO & RBAC

รองรับมาตรฐาน SOC 2

สำรวจ Apidog Enterprise

Rust ช่วยให้คุณสร้าง HTTP เซิร์ฟเวอร์ที่รวดเร็วและปลอดภัยในไม่กี่ร้อยบรรทัด สิ่งที่ไม่ได้ให้มาคือวงจรการตอบกลับที่รวดเร็วสำหรับการทดสอบเซิร์ฟเวอร์นั้น วงจรการคอมไพล์ใช้เวลานาน, cargo test จะรันทุกอย่างใหม่เมื่อมีการเปลี่ยนแปลง trait เพียงครั้งเดียว และเฟรมเวิร์ก HTTP ส่วนใหญ่ของ Rust กำหนดให้คุณต้องเขียนการทดสอบการรวม (integration test) แยกต่างหากสำหรับแต่ละ endpoint ก่อนที่คุณจะเรียกใช้งานมันแม้เพียงครั้งเดียว หากคุณต้องการส่งมอบ API ไม่ใช่แค่ไบนารี คุณจำเป็นต้องมีเครื่องมือที่อยู่นอกเครื่องมือของ Rust และสามารถสื่อสารกับเซิร์ฟเวอร์ที่กำลังทำงานอยู่ได้

คู่มือนี้จะอธิบายขั้นตอนการทดสอบ Rust API ทั้งหมดภายใน Apidog: การเชื่อมต่อ Apidog กับเซิร์ฟเวอร์ Axum หรือ Actix ของคุณ, การสร้างคำขอ (requests) ไปยัง endpoints ของคุณ, การตรวจสอบ JSON ที่แปลงด้วย Serde, การจัดการการยืนยันตัวตนด้วย JWT, การจำลอง (mocking) endpoints เพื่อให้ฝั่ง frontend ทำงานต่อไปได้ในขณะที่คุณกำลังพัฒนา handler ให้เสร็จสมบูรณ์ และการรวมทั้งหมดนี้เข้าเป็นสถานการณ์ทดสอบ CI เมื่อจบคู่มือนี้ คุณจะมีโปรเจกต์ Apidog ที่นำกลับมาใช้ซ้ำได้ ซึ่งจะช่วยตรวจจับความคลาดเคลื่อนของสัญญา (contract drift) ก่อนที่คำสั่ง cargo build --release จะเสร็จสิ้น

หากคุณเคยใช้งาน Postman หรือ curl คุณจะได้รับคุณสมบัติ "ออกแบบก่อน" (design-first) ของ Apidog ฟรี: เช่น OpenAPI spec ที่สร้างจากคำขอที่คุณบันทึกไว้, mock URL ที่สามารถแชร์ได้ และสภาพแวดล้อมสำหรับทีม ข้ามเรื่อง การย้ายจาก Postman ไปอ่านแยกต่างหาก; โพสต์นี้จะเน้นที่ Rust

สรุปย่อ

ทำไมต้องทดสอบ Rust API นอก Rust toolchain

cargo test นั้นดี แต่ก็ช้า ไม่โปร่งใสสำหรับเพื่อนร่วมทีมที่ไม่ได้ใช้ Rust และสร้างขึ้นโดยเน้นโค้ดมากกว่า HTTP หากคุณต้องการตรวจสอบว่า handler ของคุณส่งคืนสถานะโค้ดที่ถูกต้อง, รูปแบบ JSON ที่ถูกต้อง, ส่วนหัว (headers) ที่ถูกต้อง และข้อความแสดงข้อผิดพลาดที่ถูกต้องเมื่อข้อมูลที่ป้อนไม่ถูกต้อง คุณจะต้องเขียนการเรียกใช้ tower::ServiceExt::oneshot ใหม่สำหรับแต่ละกรณี จากนั้นคุณต้องดูแลรักษาสิ่งนั้นเมื่อ handler มีการเปลี่ยนแปลง และคุณต้องเขียนมันอีกครั้งใน JavaScript เพื่อให้ frontend สามารถเรียกใช้ mock ได้

Apidog มอบเลเยอร์สัญญา (contract layer) เดียวกันบนเซิร์ฟเวอร์ที่กำลังทำงานอยู่ คำขอมีอยู่เพียงครั้งเดียว การยืนยัน (Assertions) อยู่ถัดจากคำขอ เพื่อนร่วมทีม frontend เปิดโปรเจกต์เดียวกันและเห็นคำขอเดียวกับที่คุณเห็น เมื่อ Serde ได้รับแอตทริบิวต์ #[serde(rename_all = "camelCase")] ในอีกสามสัปดาห์ข้างหน้า การทดสอบที่จะล้มเหลวคือการทดสอบใน Apidog ไม่ใช่การทดสอบที่ส่งขึ้นโปรดักชัน

สามเหตุผลหลักในการเพิ่ม Apidog เข้าสู่ขั้นตอนการทำงานของ Rust:

  1. การตรวจสอบสัญญาแยกออกจากกระบวนการบิลด์. Apidog ทำงานกับไบนารีที่กำลังรันอยู่ คุณไม่จำเป็นต้องรอ rustc เพื่อตรวจสอบว่า endpoint ของคุณยังคงส่งคืน 200
  2. Mocks สามารถแชร์ได้. นักพัฒนา frontend ในเขตเวลาอื่นได้รับ URL ที่ส่งคืน JSON ที่ถูกต้อง ไม่ใช่ข้อความ Slack ที่บอกว่า “handler ยังไม่เสร็จ”
  3. OpenAPI ฟรี. Apidog สามารถสร้างเอกสาร OpenAPI 3.1 จากคำขอที่บันทึกไว้ คุณสามารถส่งมอบสิ่งนี้ให้กับใครก็ตามที่ต้องการ typed client โดยไม่ต้องเขียนคำอธิบายประกอบ utoipa หรือ aide บนทุกเส้นทาง

ขั้นตอนที่ 1: เพิ่ม Rust เซิร์ฟเวอร์ของคุณเป็น Apidog environment

เริ่มต้น Rust API ของคุณ สำหรับโปรเจกต์ 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();
}

เปิด Apidog สร้างโปรเจกต์ใหม่ จากนั้นเปิด Environment Management (เมนูดร็อปดาวน์ด้านบนขวา) และเพิ่ม environment ชื่อ Rust Local:

ตัวแปร ค่า
baseUrl http://localhost:3000
token เว้นว่างไว้ก่อน
apiVersion v1

เพิ่ม environment ที่สองชื่อ Rust Staging พร้อมกับ base URL ที่ใช้งานจริง Apidog จะกำหนดขอบเขตตัวแปรต่อ environment ดังนั้นคุณสามารถสลับจาก local ไป staging ได้ด้วยการคลิกดรอปดาวน์เพียงครั้งเดียว ไม่ต้องค้นหาและแทนที่ผ่านคำขอที่บันทึกไว้

ขั้นตอนที่ 2: เรียกใช้งาน endpoint แรก

สร้างโฟลเดอร์ชื่อ Rust API ภายในโปรเจกต์ จากนั้นสร้างคำขอใหม่:

กด Send หากเซิร์ฟเวอร์ของคุณกำลังทำงานอยู่ คุณจะได้รับ 200 พร้อม body เป็น ok บันทึกสิ่งนี้เป็น health-check นี่คือ smoke test ที่ง่ายที่สุด และยืนยันว่า environment และ base URL ทำงานได้ก่อนที่คุณจะเขียนอะไรที่ซับซ้อนกว่านี้

หากคุณได้รับข้อผิดพลาด connection refused แสดงว่าเซิร์ฟเวอร์ของคุณไม่ได้ผูกกับ 0.0.0.0 หรือพอร์ตไม่ถูกต้อง TcpListener::bind("127.0.0.1:3000") ค่าเริ่มต้นของ Rust จะปฏิเสธคำขอที่มาจากสิ่งใดก็ตามที่ถูก resolve เป็น localhost บนอินเทอร์เฟซอื่น; ให้ผูกกับ 0.0.0.0 สำหรับการพัฒนาในเครื่อง เพื่อให้ Apidog และ Docker containers สามารถเข้าถึงได้

ขั้นตอนที่ 3: ทดสอบคำขอและคำตอบ JSON ด้วย Serde

รูปแบบ Rust API ที่พบบ่อยที่สุดคือ handler แบบ JSON-in, JSON-out ที่สนับสนุนโดย Serde struct เพิ่มเส้นทาง 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));

ใน Apidog สร้างคำขอ:

{
  "name": "Ada Lovelace",
  "email": "ada@example.com"
}

ส่งคำขอ คุณจะได้รับ JSON ของ User กลับมา บันทึกเป็น create-user

ตอนนี้เปิดแท็บ Tests และเพิ่ม assertions:

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

ครั้งต่อไปที่มีคนเพิ่ม #[serde(rename_all = "camelCase")] ไปยัง struct และรูปแบบการตอบกลับของคุณเปลี่ยนจาก user_id เป็น userId การทดสอบนี้จะล้มเหลวก่อนที่จะมีการใช้งานการเปลี่ยนแปลง นั่นคือสัญญาที่ Apidog มอบให้คุณ ซึ่ง cargo test ไม่ได้ให้ เพราะ cargo test รันโค้ด Rust ของคุณกับ Rust types ของคุณ และจะผ่านได้ไม่ว่าจะเป็นรูปแบบใดก็ตาม

ขั้นตอนที่ 4: ครอบคลุมกรณีที่ Serde ปฏิเสธ (rejection cases)

ส่วนที่น่าสนใจของการจัดการ JSON ใน Rust คือสิ่งที่ Serde ทำกับอินพุตที่ไม่ถูกต้อง โดยค่าเริ่มต้น Axum จะส่งคืน 422 Unprocessable Entity โดยไม่มีรายละเอียด สร้างคำขอสามรายการที่ตั้งใจจะทำให้ schema เสียหาย:

คำขอ Body คาดหวัง
create-user-missing-email { "name": "Ada" } 422, body กล่าวถึง missing field email
create-user-extra-field { "name": "Ada", "email": "a@b.c", "admin": true } 200 หากไม่มี #[serde(deny_unknown_fields)]; มิฉะนั้น 422
create-user-wrong-type { "name": 1, "email": "a@b.c" } 422, กล่าวถึง invalid type: integer

ยืนยันสถานะโค้ดแต่ละรายการใน Tests นี่เป็นวิธีที่ประหยัดที่สุดในการจัดทำเอกสารนโยบายการตรวจสอบความถูกต้องที่แท้จริงของคุณ หากคุณเปิดใช้งาน deny_unknown_fields ในภายหลัง การทดสอบที่สองจะเปลี่ยนเป็นสีแดงและแจ้งให้คุณทราบว่า public contract มีการเปลี่ยนแปลง

ขั้นตอนที่ 5: ทดสอบเส้นทางที่ป้องกันด้วย JWT

Rust API ส่วนใหญ่ที่ใช้งานจริงจะซ่อน handler ไว้หลัง auth middleware axum-extra JWT extractor ของ 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() }))
}

ใน Apidog คุณไม่จำเป็นต้องสร้าง JWT ด้วยมือทุกครั้งที่รันการทดสอบ สร้าง Pre-Request Script ในโฟลเดอร์:

const jwt = require("jsonwebtoken");
const token = jwt.sign(
  { sub: 1, exp: Math.floor(Date.now() / 1000) + 3600 },
  "secret"
);
pm.environment.set("token", token);

เปิดการตั้งค่าโฟลเดอร์, ตั้งค่า Auth เป็น Bearer Token, ค่า {{token}} คำขอทุกรายการในโฟลเดอร์นี้จะลงนามและแสดง JWT ใหม่ ข้อผิดพลาดที่เกิดจากโทเค็นเก่าจะหายไปจากการทดสอบของคุณ สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับการยืนยันตัวตน โปรดดู วิธีทดสอบการยืนยันตัวตนด้วย JWT ใน API

ขั้นตอนที่ 6: ทดสอบ streaming และ Server-Sent Events

เฟรมเวิร์กเว็บของ Rust มีการสตรีมมิ่งเป็นคุณสมบัติหลัก การตอบกลับ Sse ของ Axum จะห่อหุ้ม futures::Stream และปล่อยชิ้นส่วน text/event-stream รูปแบบของข้อมูลคือ data: { ... }\n\n ต่อเฟรม ซึ่งจะสิ้นสุดเมื่อการเชื่อมต่อปิดลง หรือมีเหตุการณ์ "done" ชัดเจน

คำขอที่ใช้สิ่งนี้ดูเหมือนคำขอ GET ทั่วไป แต่แผงการตอบกลับใน Apidog จะเปลี่ยนเป็นโหมดสตรีมมิ่งเมื่อ Content-Type เป็น text/event-stream คุณจะเห็นแต่ละเฟรมเมื่อมาถึง พร้อมกับ timestamp นี่คือมุมมองที่คุณต้องการเมื่อกำลังแก้ไขปัญหา backpressure หรือ flush ที่หายไป

สิ่งที่ต้องยืนยัน:

หาก endpoint ของคุณใช้ WebSockets แทน SSE, Apidog มีประเภทคำขอ WebSocket แยกต่างหาก รูปแบบการทำงานเหมือนกัน: สร้างการเชื่อมต่อเพียงครั้งเดียว, บันทึกลำดับข้อความ, และยืนยันการตอบกลับ

ขั้นตอนที่ 7: จำลอง (Mock) Rust API สำหรับการพัฒนา frontend แบบคู่ขนาน

Frontend มักจะไม่ถูกบล็อกด้วยเวลาคอมไพล์ของ Rust แต่จะถูกบล็อกด้วย handler ที่ยังไม่มีอยู่ Apidog mocks ช่วยให้คุณเผยแพร่ URL ที่เสถียร ซึ่งส่งคืนสัญญาที่คุณและ frontend ตกลงกันไว้ ก่อนที่ handler จะถูกนำไปใช้งานจริง

คลิกขวาที่ create-user, เลือก Smart Mock, และเปิดใช้งาน Apidog จะส่งคืนการตอบกลับ User แบบสังเคราะห์ที่ https://mock.apidog.com/m1/<projectId>/users Mock body จะตรงกับตัวอย่างที่คุณบันทึกไว้ Mock URL ยอมรับรูปแบบ body เดียวกัน ดังนั้น frontend สามารถ POST ไปยังมันได้ราวกับว่าเป็น Rust เซิร์ฟเวอร์จริง

สำหรับการจำลองแบบไดนามิก (dynamic mocks) ให้เปลี่ยนไปที่ Advanced Mock และเขียนสคริปต์:

return {
  id: Math.floor(Math.random() * 10000),
  name: body.name,
  email: body.email,
  createdAt: new Date().toISOString()
};

Mock นั้นจะตอบสนองต่อสิ่งที่ frontend ส่งมา โดยมี id และ timestamp ที่สร้างขึ้น เมื่อ Rust handler พร้อมใช้งาน frontend จะเปลี่ยน base URL กลับไปที่ http://localhost:3000 และไม่มีอะไรเปลี่ยนแปลง สำหรับข้อมูลเพิ่มเติมเกี่ยวกับรูปแบบนี้ ทีมงานยังได้กล่าวถึง การสร้างและทดสอบ Spring Boot API และ ขั้นตอนการทดสอบ API ทั่วไป; เป็นแนวคิดเดียวกัน แต่ใช้รันไทม์ต่างกัน

ขั้นตอนที่ 8: บันทึกเป็น CI test scenario

Apidog Test Scenarios จะเชื่อมโยงคำขอต่างๆ ด้วยตัวแปรที่ใช้ร่วมกัน และรันแบบ headless สร้าง scenario:

  1. health-check, ยืนยัน 200
  2. create-user, ยืนยัน 200, เก็บ body.id เข้าสู่ตัวแปร
  3. create-user-missing-email, ยืนยัน 422
  4. me (พร้อมกับ JWT pre-request), ยืนยัน 200 และ id ที่ส่งคืนตรงกับ id ที่เก็บไว้
  5. คำขอ SSE, ยืนยันว่า stream เสร็จสมบูรณ์ภายใน 5 วินาที

ส่งออก scenario เป็น JSON, คอมมิตเข้าสู่ repo ของคุณภายใต้ tests/apidog/, และเรียกใช้จาก 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"

ทุก PR ที่เกี่ยวข้องกับ handler จะถูกรันกับ Rust binary ที่ใช้งานจริง พร้อมกับชุด contract เต็มรูปแบบ หากการเปลี่ยนชื่อ Serde, การเปลี่ยนแปลงสถานะโค้ด, หรือการปรับแต่งการตรวจสอบ JWT ทำให้ public shape เสียหาย CI จะตรวจจับได้ก่อนที่ปุ่ม merge จะเปลี่ยนเป็นสีเขียว

ขั้นตอนที่ 9: สร้าง OpenAPI จากคำขอที่บันทึกไว้

เมื่อชุดคำขอมีความเสถียรแล้ว ให้เปิดเมนู Export ของ Apidog และเลือก OpenAPI 3.1 คุณจะได้เอกสาร spec ที่ครอบคลุมทุกคำขอที่บันทึกไว้ พร้อมตัวอย่าง body ที่คุณส่งไป มอบสิ่งนี้ให้กับใครก็ตามที่กำลังสร้าง typed client (TypeScript, Swift, Kotlin, Python) แล้วพวกเขาจะได้ contract ที่ตรงกับสิ่งที่ Rust เซิร์ฟเวอร์ของคุณส่งคืนในวันนี้ ไม่ใช่สิ่งที่ใครบางคนเขียนด้วยมือในไฟล์ .yaml เมื่อหกเดือนที่แล้ว

หากคุณต้องการให้ spec ถูกตรวจสอบเข้าสู่ Rust repo ของคุณ ให้รัน apidog-cli export จาก CI และเขียนลงใน openapi.json การ cargo build ครั้งต่อไปจะไม่เปลี่ยนแปลง แต่ผู้ใช้งาน API ทุกคนจะได้รับข้อมูลที่ถูกต้องบนดิสก์

คำถามที่พบบ่อย

สรุป

Rust API สมควรได้รับวงจรตอบกลับที่ไม่ต้องรอคอมไพเลอร์ ชุดคำขอใน Apidog มอบวงจรนั้นให้คุณ: HTTP จริง, การยืนยันจริง, mocks จริงสำหรับ frontend และ CI scenario ที่รันกับไบนารีที่ทำงานอยู่ สร้างคำขอข้างต้นเพียงครั้งเดียว และการเปลี่ยนแปลงใดๆ ในอนาคตของ Axum หรือ Actix handler ของคุณจะกลายเป็นการทดสอบที่ควบคุมได้ แทนที่จะเป็นความประหลาดใจระหว่างรันไทม์

ดาวน์โหลด Apidog และเชื่อมต่อไปยัง Rust เซิร์ฟเวอร์ของคุณ การตั้งค่าใช้เวลาน้อยกว่าสิบนาที ผลลัพธ์ที่ได้คือสัญญาที่คุณควบคุมได้ แยกออกจาก cargo และทีม frontend ที่ไม่ต้องถามอีกต่อไปว่า handler เสร็จเมื่อไหร่

ฝึกการออกแบบ API แบบ Design-first ใน Apidog

ค้นพบวิธีที่ง่ายขึ้นในการสร้างและใช้ API