Тестування Rust Cloudflare Workers
October 20, 2025 · Edit this page on GitHub
Одна з причин чому не люблю усіляку логіку в конфігах балансера це складність тестування змін. Тому пишу крізь сльози щастя: можу створювати роутери на справжній мові під cloudflare workers.
Очевидно1 що в якості мови я обрав Rust.
Добре, неочевидно. Та і я не одразу обрав Rust. Спроби швиденько пописати на JS були. Але помилки супер незрозумілі, нема типів і ітераторів, це що, 2002 рік на дворі? Можна було б обрати тайпскрипт, але я його зовсім не знаю, тому і раст.
Тестування fetch()
Перше що приходить на думку (і майже завжди правильно) це просто смикати усього воркера і дивитись що воно віддає.
Наприклад у нас є такий собі воркер:
use *;
async
Можемо написати дуже простий код на тайпскрипт + vitest:
;
;
"Worker",;
І запустити цей код за допомогою pnpm test
Що тільки що відбулося?
Cloudflare workers це V8 2, який запускає джаваскріпт код. Якщо ми використовуємо vitest з лібою cloudflare:test то воно вміє запускати двіжок воркерів, який потім запускає збілджений Rust код і ми просто смикаємо фетч апі (в утці заєць, а в зайці голка).
Ось тут більше деталей про ізоляцію і взагалі про те що відбувається.
Інтеграційні тести
Для простих сценаріїв такого має бути взагалі достатньо. Але ми ж не шукаємо простих шляхів. Наша доля складна і буремна, нам потрібні виклики. Наприклад, виклики бази данних.
Важливо: далі функції мають виключно демонстраційний і спрощений характер. Це зроблено задля того щоб ви не відволікались на складність самої логіки, а дивились на демонтрацію механіки.
Давай уявимо що у нас є код який рахує скільки разів користувач смикнув воркер і далі виконує якусь логіку на цьому:
use *;
async
async
Це вже складніше тестувати з fetch(), тому що далі логіка може бути досить складною, а кожена варіація збільшує кількість сценаріїв тестування 3
Наприклад, у нас є метод fetch() який послідовно викликає функції A, B, C і використовує результати однієї функції як аргументи до наступної.
Тоді,
A = {a₁, a₂, a₃, a₄} // 4 можливі стани функції
B = {b₁, b₂, b₃} // 3 можливі стани функції B
C = {c₁, c₂, c₃} // 3 можливі стани функції C
A × B × C = {(aᵢ, bⱼ, cₖ) | aᵢ ∈ A, bⱼ ∈ B, cₖ ∈ C}
|A × B × C| = |A| × |B| × |C| = 4 × 3 × 3 = 36 комбінацій
В той самий час як індивідуальне тестування кожної функції принесе всього 10 комбінацій. Це набагато менше ніж 36.
І так, я розумію що в реальному світі буде менш ніж 36 комбінацій, але точно більше ніж 10.
Тому має більший сенс тестувати кожну функцію окремо.
wasm_bindgen
Для того щоб передати об’єкт бази данних до функції number_of_requests() треба спочатку згенерити біндінги 4
Уяви, що в тебе є два друга які спілкуються різними мовами. Rust каже "я контролюю пам'ять", а JavaScript каже "я взагалі не знаю що таке пам'ять, але у мене є undefined". І от вони хочуть разом щось зробити. Біндінги — це той перекладач, який сидить між ними і перекладає їхні божевільні розмови. Хоча я б таких друзів в дурку здав. І залишився б без друзів.
Для цього можна використати існуючі макроси з wasm_bindgen. Тоді для такої функції:
За допомогою команди worker-build --release будуть згенеровані біндінги в typescript і javascript:
/* tslint:disable */
/* eslint-disable */
;
;
;
...
Складні об’єкти
Але наша функція async fn number_of_requests(db: D1Database, user_id: i32) -> Result<i32> приймає базу данних як вхідний параметр. І якщо ми додамо макрос `#[wasm_bindgen] то отримаємо помилку
the trait bound `worker::D1Database: worker::wasm_bindgen::convert::FromWasmAbi` is not satisfied [E0277]
Help: the following other types implement trait `worker::wasm_bindgen::convert::FromWasmAbi`:
// #[cfg(debug_assertions)] Для того щоб генерувати обгортку лише
// в режимі тестування
use *;
pub async
Тобто ми точно очікуємо лише базу данних, тому на вхід отримуємо якийсь об’єкт, який потім конвертуємо в базу данних 5.
можливо і потрібно створювати біндінги і під об’єкти у вашому rust коді. Наприклад
use ;
use *;
Тепер ця структура доступна і з тайпскрипту.
Завдяки цьому тепер ми можемо написати тести
;
;
; // шлях до біндінгів
; // шлях до WASM файлу
'wrap_number_of_requests - integration tests',;
Треба тільки не забувати перед кожним запуском тестів білдити код і генерувати біндінги:
# якщо наш код тестів в ./test/, то
Я просто запхав усе необхідне в Justfile, але хто як любить.
Сподіваюсь тепер писати на расті під воркери буде простіше. Та і vitest тести мені дуже сподобались, може і вам зайде.