0Pricing
Learn Rust Coding · Урок

Тестирование API

Интеграционные тесты

«Тестирование API» — бесплатный урок Learn Rust Coding на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения 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 реализуют трейт Service Tower, поэтому Вы можете напрямую передавать им запросы, не привязывая порт. Метод 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/. Каждый файл в ней компилируется как отдельный пакет, использующий открытый 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 в CI, чтобы обнаруживать регрессии до развёртывания.

Быстрая проверка

Проверьте, насколько хорошо Вы поняли тестирование API.

Итоги

Вы научились тестировать REST API на Rust:

  • Тестируйте чистую логику модульными тестами; для асинхронного кода используйте #[tokio::test].
  • oneshot напрямую запускает маршрутизатор, поэтому порт не нужен.
  • Создавайте запросы с телами и заголовками; собирайте и разбирайте тела ответов.
  • Используйте отдельный конструктор приложения для каждого теста; помещайте интеграционные тесты в tests/.
  • #[sqlx::test] создаёт чистые базы данных; проверяйте успешные сценарии, ошибки и граничные случаи.

Часто задаваемые вопросы

Урок «Тестирование API» бесплатный?

Да — полный текст урока «Тестирование API» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Learn Rust Coding, подпишись на CoddyKit PRO. Курс Learn Rust Coding содержит 4 уроков всего.

Чему я научусь в уроке «Тестирование API»?

Интеграционные тесты Ты практикуешь Learn Rust Coding с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Learn Rust Coding?

Предыдущий опыт не требуется. Learn Rust Coding на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.

Сколько времени занимает урок «Тестирование API»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Learn Rust Coding?

Да. Каждый урок Learn Rust Coding включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Настройка проекта
  2. Конечные точки и модели
  3. Интеграция с базой данных
  4. Тестирование API
← Назад к Learn Rust Coding