跳到主要内容

Web 测试

问题

如何测试 Rust Web 应用?

答案

axum 集成测试

axum 提供了直接测试 Router 的方式,无需启动 HTTP 服务器:

#[cfg(test)]
mod tests {
use super::*;
use axum::{body::Body, http::{Request, StatusCode}};
use tower::ServiceExt; // for oneshot()
use http_body_util::BodyExt;

fn app() -> Router {
Router::new()
.route("/users", get(list_users).post(create_user))
.route("/users/{id}", get(get_user))
.with_state(test_state())
}

#[tokio::test]
async fn test_create_user() {
let app = app();

let response = app
.oneshot(
Request::builder()
.method("POST")
.uri("/users")
.header("Content-Type", "application/json")
.body(Body::from(r#"{"name":"Alice","age":30}"#))
.unwrap(),
)
.await
.unwrap();

assert_eq!(response.status(), StatusCode::CREATED);
}

#[tokio::test]
async fn test_get_user_not_found() {
let app = app();

let response = app
.oneshot(
Request::builder()
.uri("/users/999")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();

assert_eq!(response.status(), StatusCode::NOT_FOUND);

// 读取响应体
let body = response.into_body().collect().await.unwrap().to_bytes();
let error: serde_json::Value = serde_json::from_slice(&body).unwrap();
assert_eq!(error["error"]["code"], 404);
}
}

测试策略

层级测试什么工具
单元测试Service 层逻辑#[test], mock
集成测试API 端到端Router::oneshot
数据库测试SQL 查询事务回滚、testcontainers

数据库测试

#[sqlx::test]  // 自动创建测试数据库、运行迁移、测试后回滚
async fn test_create_user(pool: PgPool) {
let user = create_user(&pool, "Alice").await.unwrap();
assert_eq!(user.name, "Alice");
}

常见面试问题

Q1: 如何 Mock 外部依赖?

答案

通过 trait 抽象 + 依赖注入:

// 定义 trait
#[async_trait]
trait UserRepo: Send + Sync {
async fn find(&self, id: u64) -> Option<User>;
}

// 生产实现
struct PgUserRepo { pool: PgPool }

// Mock 实现
struct MockUserRepo { users: Vec<User> }

#[async_trait]
impl UserRepo for MockUserRepo {
async fn find(&self, id: u64) -> Option<User> {
self.users.iter().find(|u| u.id == id).cloned()
}
}

// Handler 依赖 trait 而非具体类型
async fn get_user(
State(repo): State<Arc<dyn UserRepo>>,
Path(id): Path<u64>,
) -> Result<Json<User>, AppError> { /* ... */ }

相关链接