Cloudflare Workers
Testing
Vitest
Miniflare
CI/CD

Testing Workers : unité, intégration et comment simuler le runtime sans dépendre de Cloudflare

Le runtime Workers n'est pas Node.js - les tests sans en tenir compte garantissent que les tests réussissent localement et que des bogues apparaissent en production.

Testing Workers : unité, intégration et comment simuler le runtime sans dépendre de Cloudflare

Tester Workers avec Jest exécuté sur Node.js est un piège élégant. Les tests réussissent, le CI devient vert et le code arrive en production avec des bogues que l'environnement de test n'a jamais pu détecter, car l'environnement de test n'est pas le moteur d'exécution qui exécute le code en production. Les Workers s'exécutent sur des isolats V8 avec les API de la plateforme Web, pas les API Node.js : il n'y a pas de Buffer, il n'y a pas de process.env, le fetch est l'implémentation native du runtime (pas le node-fetch ni le undici), et crypto est l'API Web Crypto avec ses signatures asynchrones. Une simulation fetch écrite pour Jest dans Node.js peut réussir les tests et ne pas fonctionner silencieusement lorsque le Worker traite le vrai Response depuis la périphérie.

Pourquoi l'environnement de test est plus important qu'il n'y paraît

La différence entre Node.js et le runtime Workers va au-delà des API disponibles : le comportement Edge n'apparaît que dans des conditions spécifiques. Le TextEncoder dans Node.js et le TextEncoder dans le runtime Workers ont la même interface, mais le Uint8Array qu'ils produisent peut se comporter différemment lorsqu'il est transmis à une opération SubtleCrypto en fonction de la version et de l'implémentation. Workers Headers a un comportement de normalisation des noms d'en-tête (insensible à la casse, ordre lexicographique) qui diffère des implémentations fictives.

Plus important encore : les liaisons n'existent pas dans Node.js. env.KV, env.DB, env.BUCKET sont des objets injectés par le runtime — il n'existe aucun moyen d'importer un package npm qui leur fournisse une fidélité totale. Les simulations manuelles testent le code qui appelle la simulation, et non le code qui fonctionnera avec la liaison réelle.

@cloudflare/vitest-pool-workers résout ce problème en exécutant les tests dans un environnement d'exécution Workers réel, en particulier Miniflare v3, qui exécute un véritable isolat V8, et non une simulation Node.js. Les tests ont accès à caches, crypto, fetch, Headers, ReadableStream, TransformStream avec les implémentations d'exécution. Les liaisons sont fournies sous forme d'implémentations en cours : KV en mémoire, D1 en tant que SQLite intégré, R2 en tant que stockage local. Le résultat est qu'un test qui réussit avec vitest-pool-workers teste du code qui fonctionnera dans le temps d'exécution réel.

Configuration de vitest.config.ts avec des liaisons

La configuration du pool de travailleurs dans la vitest.config.ts est l'endroit où vous déclarez les liaisons que les tests recevront. La structure reflète wrangler.toml, mais dans la configuration Vitest :

import { defineConfig } from 'vitest/config'; import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config'; export default defineWorkersConfig({ test: { poolOptions: { workers: { wrangler: { configPath: './wrangler.toml' }, miniflare: { kvNamespaces: ['CACHE'], d1Databases: ['DB'], r2Buckets: ['BUCKET'], bindings: { APP_ENV: 'test', }, }, }, }, }, });

Avec cette configuration, chaque suite de tests reçoit un environnement propre avec un espace de noms KV vide, une base de données D1 SQLite vide (avec le schéma appliqué si vous configurez migrations) et un compartiment R2 vide. Les liaisons sont isolées entre les suites : les modifications apportées par les tests dans une suite ne sont pas transmises à une autre.

Pour accéder aux liaisons dans les tests, @cloudflare/vitest-pool-workers exporte un assistant env que Miniflare injecte via un mécanisme global :

import { env } from 'cloudflare:test'; test('deve retornar 404 para usuário inexistente', async () => { const request = new Request('https://api.example.com/users/999'); const response = await SELF.fetch(request); expect(response.status).toBe(404); });

SELF est la liaison du Worker lui-même testé — vous pouvez faire SELF.fetch() pour tester le gestionnaire de récupération en tant qu'appel HTTP d'intégration, avec un routage complet et toutes les liaisons disponibles.

La division entre les tests unitaires et les tests d'intégration

Les fonctions de logique métier qui fonctionnent sur des valeurs JavaScript pures (analyse d'un JWT, calcul d'un prix réduit, validation d'un schéma d'entrée) peuvent et doivent être testées sans le pool Workers. Ces fonctions ne dépendent pas des API ou des liaisons d'exécution, et les tester dans Node.js pur avec Vitest standard est plus rapide et plus simple. La règle générale : si la fonction prend et renvoie des valeurs primitives JavaScript ou des objets plats, elle appartient au pool Node.js. S'il utilise Request, Response, Headers, ReadableStream ou toute autre liaison, il appartient au pool Workers.

Les gestionnaires HTTP d'intégration (la fonction qui est le point d'entrée du Worker) ont besoin que le pool de Worker soit testé fidèlement. Tester un gestionnaire d'intégration signifie aller SELF.fetch(new Request(...)) et vérifier l'état, les en-têtes de réponse et le corps de la réponse que le Worker produirait en production. Ce test a également un effet secondaire sur les liaisons : si le gestionnaire doit sauvegarder un enregistrement dans D1, le test peut le faire plus tard et vérifier que les données ont été écrites correctement.

CI sans informations d'identification Cloudflare — et les limites de Miniflare

vitest-pool-workers avec Miniflare s'exécute entièrement en local : il n'effectue aucun appel à l'API Cloudflare et n'a pas besoin de CLOUDFLARE_ACCOUNT_ID ou CLOUDFLARE_API_TOKEN dans le CI. Miniflare v3 télécharge workerd (le runtime open source de Cloudflare) en tant que binaire local et l'exécute comme n'importe quel autre processus. Le pipeline CI se résume à npm ci et npx vitest run — aucun secret, aucune dépendance réseau au-delà du téléchargement binaire lors de la première installation.

Miniflare v3 simule le runtime avec une haute fidélité, mais il existe des exceptions documentées qui sont importantes pour la stratégie de test. Les objets durables en mode production – avec leur garantie singleton globale – ne sont pas simulés avec une cohérence distribuée ; Miniflare crée une instance locale utile pour la logique de base, mais ne teste pas le comportement réel de coordination. Les comportements CDN tels que la mise en cache périphérique et l'objet cf avec des données de géolocalisation réelles doivent être exercés wrangler dev --remote.

Dans ces cas, la réponse consiste à organiser des tests de fumée qui effectuent des appels HTTP directs à un Worker intermédiaire après le déploiement. Ils ne remplacent pas les tests Miniflare : ils couvrent ce que Miniflare ne peut pas simuler : des comportements qui n'existent que lorsque le code s'exécute sur une infrastructure réelle.

Tests waitUntil et travail en arrière-plan

Le ctx.waitUntil() est l'endroit où le travail a lieu après l'envoi de la réponse. Le SELF.fetch() dans les tests renvoie la réponse immédiatement, mais le travail passé au waitUntil risque de ne pas être terminé. @cloudflare/vitest-pool-workers fournit waitOnExecutionContext pour résoudre ce problème :

import { env, SELF, waitOnExecutionContext } from 'cloudflare:test'; test('deve enfileirar analytics em background', async () => { const ctx = createExecutionContext(); const response = await SELF.fetch(new Request('https://api.example.com/checkout')); await waitOnExecutionContext(ctx); const queued = await env.ANALYTICS_QUEUE.read(); expect(queued).toHaveLength(1); });

Sans waitOnExecutionContext, un test qui vérifie l'effet secondaire de waitUntil aura une condition de concurrence critique – une desquamation difficile à diagnostiquer car le problème ne réside pas dans le code de production, mais dans le code de test.

A lire aussi

-Cloudflare Workers en production : ce qui change après hello world