0Pricing
Learn Rust Coding · 课时

测试 API

集成测试

测试 API 是 CoddyKit 上的免费 Learn Rust Coding 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Learn Rust Coding 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Learn Rust Coding 课程共包含 4 节课。

为什么要测试 API?

测试能让您确信端点的行为正确,并且在修改代码后仍能正常工作。对于 REST API,最有价值的测试是集成测试:它们会端到端地运行真实路由,发送请求并对响应进行断言。

本课将介绍单元测试、Tower 的 oneshot 技巧,以及完整的集成测试。

纯逻辑的单元测试

不接触网络的逻辑(例如验证)可以使用普通的 Rust 单元测试进行测试。将它们放在代码旁边的 #[cfg(test)] 模块中。这些测试通过 cargo test 可以快速运行。

fn validate_title(title: &str) -> bool {
    !title.trim().is_empty()
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn rejects_empty() {
        assert!(!validate_title("   "));
        assert!(validate_title("buy milk"));
    }
}

异步测试

处理程序是异步的,因此需要等待的测试函数必须在运行时上运行。请使用 #[tokio::test],而不是 #[test]。它会为该测试启动 Tokio 运行时,让您可以通过 .await 调用异步代码。

async fn add(a: i32, b: i32) -> i32 { a + b }

#[cfg(test)]
mod tests {
    use super::*;
    #[tokio::test]
    async fn adds() {
        assert_eq!(add(2, 3).await, 5);
    }
}

不使用网络进行测试

Axum 路由实现了 Tower 的 Service 特征,因此您可以直接向它们传入请求,而无需绑定端口。oneshot 方法接收一个 Request,并返回 Response。这样可以让集成测试快速且具有确定性。

// dev-dependencies needed: tower (for ServiceExt), http-body-util
use axum::{Router, routing::get};
use axum::http::{Request, StatusCode};
use axum::body::Body;
use tower::ServiceExt; // brings in oneshot

async fn check() {
    let app = Router::new().route("/health", get(|| async { "ok" }));
    let res = app
        .oneshot(Request::builder().uri("/health").body(Body::empty()).unwrap())
        .await.unwrap();
    assert_eq!(res.status(), StatusCode::OK);
}

断言状态码

大多数测试首先检查 HTTP 状态。缺少的资源应返回 404,成功创建应返回 201,错误的请求体应返回 400。为该路由构建请求,并将 res.status() 与预期代码进行比较。

use axum::http::{Request, StatusCode};
use axum::body::Body;
use tower::ServiceExt;

async fn missing_returns_404(app: axum::Router) {
    let res = app
        .oneshot(Request::builder()
            .uri("/todos/999")
            .body(Body::empty()).unwrap())
        .await.unwrap();
    assert_eq!(res.status(), StatusCode::NOT_FOUND);
}

读取响应体

要对 JSON 进行断言,请将响应体收集为字节,然后将其反序列化。http_body_util::BodyExt::collect 辅助工具会收集请求体,随后 serde_json 将其解析为您的模型,以便对各个字段进行断言。

use http_body_util::BodyExt;

async fn read_json(res: axum::http::Response<axum::body::Body>) {
    let bytes = res.into_body().collect().await.unwrap().to_bytes();
    let todo: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
    assert_eq!(todo["done"], false);
}

发送 JSON 请求体

要测试 POST,请使用 JSON 请求体和正确的内容类型标头构建请求。序列化您的输入结构体,设置 content-type: application/json,然后通过 oneshot 传递它。

use axum::http::{Request, StatusCode, header};
use axum::body::Body;
use tower::ServiceExt;

async fn create_returns_201(app: axum::Router) {
    let body = serde_json::json!({ "title": "test" }).to_string();
    let res = app
        .oneshot(Request::builder()
            .method("POST").uri("/todos")
            .header(header::CONTENT_TYPE, "application/json")
            .body(Body::from(body)).unwrap())
        .await.unwrap();
    assert_eq!(res.status(), StatusCode::CREATED);
}

可复用的测试应用构建器

每个测试都应从干净的状态开始。编写一个辅助工具,用新的内存数据库或测试数据库构建全新的路由。每个测试都调用它,可以将测试彼此隔离,使一个测试无法影响另一个测试。

use axum::Router;
use std::sync::{Arc, Mutex};

fn test_app() -> Router {
    let store = Arc::new(Mutex::new(Vec::new()));
    build(store) // same build() the real server uses
}

tests 目录

集成测试位于顶层的 tests/ 文件夹中。其中的每个文件都会作为单独的 crate 编译,并使用您的库的公共 API。这样可以强制您通过真实用户所看到的相同接口进行测试。

  • tests/api.rs 存放您的端点测试。
  • 使用 cargo test 运行全部测试。
// tests/api.rs
use my_api::build_router; // exported from lib.rs

#[tokio::test]
async fn health_ok() {
    let _app = build_router();
    // send a request and assert ...
}

针对数据库进行测试

当处理程序使用 sqlx 时,测试也需要数据库。常见策略包括:专用测试数据库、每次测试结束后回滚的事务,或者使用 sqlx 的 #[sqlx::test] 宏,它会为每个测试自动准备干净的数据库。

// requires sqlx test features and a DATABASE_URL
use sqlx::PgPool;

#[sqlx::test]
async fn inserts_todo(pool: PgPool) {
    let todo = create(&pool, "learn rust").await.unwrap();
    assert_eq!(todo.title, "learn rust");
    assert_eq!(todo.done, false);
}

测试哪些内容

目标是构建均衡的测试套件:

  • 正常路径:有效请求返回正确的状态和请求体。
  • 错误:缺少资源时返回 404,错误输入时返回 400。
  • 边界情况:空列表、边界值、重复项。

在持续集成中运行 cargo test,以便在部署前发现回归问题。

快速检查

测试您对 API 测试的理解。

回顾

您学会了如何测试 Rust REST API:

  • 对纯逻辑进行单元测试;对异步代码使用 #[tokio::test]。
  • oneshot 可直接驱动路由,无需端口。
  • 使用请求体和标头构建请求;收集并解析响应体。
  • 每个测试使用全新的应用构建器以实现隔离;将集成测试放在 tests/ 中。
  • #[sqlx::test] 会准备干净的数据库;覆盖正常路径、错误和边界情况。

常见问题解答

「测试 API」课时是免费的吗?

是的 — 「测试 API」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Learn Rust Coding 课程的其余内容,请升级到 CoddyKit PRO。 Learn Rust Coding 课程共包含 4 节课。

「测试 API」这节课中我会学到什么?

集成测试 你通过在浏览器中直接运行的动手代码来练习 Learn Rust Coding,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Learn Rust Coding 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Learn Rust Coding 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「测试 API」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Learn Rust Coding 课中编写并运行代码吗?

能。每节 Learn Rust Coding 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 项目设置
  2. 端点与模型
  3. 数据库集成
  4. 测试 API
← 返回 Learn Rust Coding