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> { /* ... */ }