← Cloudflare Workers / workers / testing / vitest-integration
Testovací API
Integrace Workers Vitest poskytuje runtime pomocníky pro psaní testů. Některé pomocníky se exportují z cloudflare:workers modul a další z cloudflare:test modul. Oba moduly poskytuje @cloudflare/vitest-plugin balíček, ale lze jej importovat pouze z testovacích souborů, které se spouští v runtime Workers.
cloudflare:workers exporty
-
env: import("cloudflare:workers").ProvidedEnv-
Zpřístupňuje
envobjekt pro použití jako druhý argument předávaný handlerům exportovaným ve formátu ES modulů. Ten poskytuje přístup k vazby které jste definovali ve svém Konfigurační soubor Vitest.
import { env } from "cloudflare:workers"; it("uses binding", async () => { await env.KV_NAMESPACE.put("key", "value"); expect(await env.KV_NAMESPACE.get("key")).toBe("value"); });Chcete-li nakonfigurovat typ této hodnoty, použijte ambientní typ modulu:
declare module "cloudflare:workers" { interface ProvidedEnv { KV_NAMESPACE: KVNamespace; } // ...or if you have an existing `Env` type... interface ProvidedEnv extends Env {} }
-
-
exports: objekt-
Poskytuje přístup k exportům
mainWorker. Použijteexports.default.fetch()k psaní integračních testů proti výchozímu export handleru vašeho Workeru.mainWorker běží ve stejném izolátu/kontextu jako testy, takže se na něj vztahují i všechny globální mocky. Na rozdíl od předchozíhoSELFbinding,exportsneposkytuje Assets. Pro testování aktiv použijtestartDevWorker().
import { exports } from "cloudflare:workers"; it("dispatches fetch event", async () => { const response = await exports.default.fetch("https://example.com"); expect(await response.text()).toMatchInlineSnapshot(...); });
-
cloudflare:test exporty
Události
-
createExecutionContext(): ExecutionContext- Vytvoří instanci
contextobjekt pro použití jako třetí argument handlerů exportovaných ve formátu ES modulů.
- Vytvoří instanci
-
waitOnExecutionContext(ctx:ExecutionContext): Promise<void>-
Toho využijte, pokud chcete počkat na všechny Promise předané do
ctx.waitUntil()se vyřeší, než se spustí testovací assertions na případné vedlejší efekty. Přijímá pouze instanceExecutionContextvrácenácreateExecutionContext().
import { env } from "cloudflare:workers"; import { createExecutionContext, waitOnExecutionContext } from "cloudflare:test"; import { it, expect } from "vitest"; import worker from "./index.mjs"; it("calls fetch handler", async () => { const request = new Request("https://example.com"); const ctx = createExecutionContext(); const response = await worker.fetch(request, env, ctx); await waitOnExecutionContext(ctx); expect(await response.text()).toMatchInlineSnapshot(...); });
-
-
createScheduledController(options?:FetcherScheduledOptions): ScheduledController-
Vytvoří instanci
ScheduledControllerpro použití jako první argument formátu modules-formatscheduled()exportovaných handlerů.
import { env } from "cloudflare:workers"; import { createScheduledController, createExecutionContext, waitOnExecutionContext } from "cloudflare:test"; import { it, expect } from "vitest"; import worker from "./index.mjs"; it("calls scheduled handler", async () => { const ctrl = createScheduledController({ scheduledTime: new Date(1000), cron: "30 * * * *" }); const ctx = createExecutionContext(); await worker.scheduled(ctrl, env, ctx); await waitOnExecutionContext(ctx); });
-
-
createMessageBatch(queueName:string, messages:ServiceBindingQueueMessage[]): MessageBatch- Vytvoří instanci
MessageBatchpro použití jako první argument formátu modules-formatqueue()exportovaných handlerů.
- Vytvoří instanci
-
getQueueResult(batch:MessageBatch, ctx:ExecutionContext): Promise<FetcherQueueResult>-
Vrátí stav potvrzení/opakování zpráv v
MessageBatch, a čeká na všechnyExecutionContext#waitUntil()ovýchPromises do vyřešení. Přijímá pouze instanceMessageBatchvrácenácreateMessageBatch(), a instanceExecutionContextvrácenácreateExecutionContext().
import { env } from "cloudflare:workers"; import { createMessageBatch, createExecutionContext, getQueueResult } from "cloudflare:test"; import { it, expect } from "vitest"; import worker from "./index.mjs"; it("calls queue handler", async () => { const batch = createMessageBatch("my-queue", [ { id: "message-1", timestamp: new Date(1000), body: "body-1" } ]); const ctx = createExecutionContext(); await worker.queue(batch, env, ctx); const result = await getQueueResult(batch, ctx); expect(result.ackAll).toBe(false); expect(result.retryBatch).toMatchObject({ retry: false }); expect(result.explicitAcks).toStrictEqual(["message-1"]); expect(result.retryMessages).toStrictEqual([]); });
-
Durable Objects
-
runInDurableObject<O extends DurableObject, R>(stub:DurableObjectStub, callback:(instance: O, state: DurableObjectState) => R | Promise<R>): Promise<R>-
Spustí zadaný
callbackuvnitř Durable Object, který odpovídá zadanémustub.
Toto dočasně nahradí
fetch()handler scallback, poté mu odešle požadavek a vrátí výsledek. Toho lze využít k volání či sledování metod Durable Object nebo k nastavení či získání uložených dat. Upozorňujeme, že to lze použít pouze sstubs odkazující na Durable Objects definované vmainWorker.
export class Counter { constructor(readonly state: DurableObjectState) {} async fetch(request: Request): Promise<Response> { let count = (await this.state.storage.get<number>("count")) ?? 0; void this.state.storage.put("count", ++count); return new Response(count.toString()); } }import { env } from "cloudflare:workers"; import { runInDurableObject } from "cloudflare:test"; import { it, expect } from "vitest"; import { Counter } from "./index.ts"; it("increments count", async () => { const id = env.COUNTER.newUniqueId(); const stub = env.COUNTER.get(id); let response = await stub.fetch("https://example.com"); expect(await response.text()).toBe("1"); response = await runInDurableObject(stub, async (instance: Counter, state) => { expect(instance).toBeInstanceOf(Counter); expect(await state.storage.get<number>("count")).toBe(1); const request = new Request("https://example.com"); return instance.fetch(request); }); expect(await response.text()).toBe("2"); });
-
-
runDurableObjectAlarm(stub:DurableObjectStub): Promise<boolean>- Okamžitě spustí a odstraní Durable Object, na který odkazuje
stub's alarm if one is scheduled. Returnstruepokud proběhl alarm, afalsejinak. Toto lze použít pouze sstubs odkazující na Durable Objects definované vmainWorker.
- Okamžitě spustí a odstraní Durable Object, na který odkazuje
-
evictDurableObject(stub:DurableObjectStub, options?:DurableObjectEvictionOptions): Promise<void>-
Vyřadí aktuálně běžící Durable Object, na který odkazuje
stub, čímž se zruší jeho instance a resetuje se stav v paměti. Ve výchozím nastavení se hibernatable WebSockets místo uzavření hibernují a eviction čeká až 30 sekund, než se dokončí probíhající požadavky.
Hodí se k testování chování Durable Object při vyřazování z paměti, například obnovení stavu z úložiště nebo pokračování v hibernovaných WebSockets.
Odmítne, pokud
stubnení zástupný objekt Durable Object, pokud cílový Durable Object aktuálně neběží, nebo pokud má jeho jmenný prostor zakázané odstranění. Toto lze použít pouze sstubs odkazující na Durable Objects definované vmainWorker.
import { env } from "cloudflare:workers"; import { evictDurableObject } from "cloudflare:test"; import { it, expect } from "vitest"; it("preserves stored data across eviction", async () => { const id = env.COUNTER.idFromName("evict-test"); const stub = env.COUNTER.get(id); // Each request increments and persists the count to storage expect(await (await stub.fetch("https://example.com")).text()).toBe("1"); expect(await (await stub.fetch("https://example.com")).text()).toBe("2"); // Evict the Durable Object. The in-memory instance is torn down, // but durable storage is preserved. await evictDurableObject(stub); // The next request reconstructs the instance and reads the persisted count expect(await (await stub.fetch("https://example.com")).text()).toBe("3"); }); -
DurableObjectEvictionOptionsrozhraní řídí chování při vyřazování z mezipaměti:Vlastnost Typ Výchozí Popis webSockets"close" | "hibernate""hibernate"Ovládá, co se stane s hibernovatelnými WebSockety při odebrání Durable Object z paměti. S "hibernate", WebSockets přejdou do hibernace a mohou pokračovat po vyřazení z paměti. S"close", WebSockets se uzavřou.
-
-
listDurableObjectIds(namespace:DurableObjectNamespace): Promise<DurableObjectId[]>-
Vrátí ID všech objektů vytvořených v
namespace. Respektuje izolaci úložiště podle jednotlivých souborů, což znamená, že objekty vytvořené v jiném testovacím souboru nebudou vráceny.
import { env } from "cloudflare:workers"; import { listDurableObjectIds } from "cloudflare:test"; import { it, expect } from "vitest"; it("increments count", async () => { const id = env.COUNTER.newUniqueId(); const stub = env.COUNTER.get(id); const response = await stub.fetch("https://example.com"); expect(await response.text()).toBe("1"); const ids = await listDurableObjectIds(env.COUNTER); expect(ids.length).toBe(1); expect(ids[0].equals(id)).toBe(true); });
-
-
reset(): Promise<void>-
Odstraní všechna data ze všech připojených bindings. To se hodí pro resetování stavu mezi testovacími bloky.
import { reset } from "cloudflare:test"; import { afterEach } from "vitest"; afterEach(async () => { await reset(); });
-
-
abortAllDurableObjects(): Promise<void>-
Resetuje všechny instance Durable Object. Na rozdíl od
reset(), toto neodstraní uložená data. Tímto se vynuceně ukončí všechny běžící instance Durable Object a zahodí se jejich stav v paměti, aniž by se čekalo na dokončení probíhajících požadavků.
import { abortAllDurableObjects } from "cloudflare:test"; import { afterEach } from "vitest"; afterEach(async () => { await abortAllDurableObjects(); });
-
-
evictAllDurableObjects(options?:DurableObjectEvictionOptions): Promise<void>-
Vyřadí všechny aktuálně běžící Durable Objects ve vyřaditelných namespacech. Na rozdíl od
abortAllDurableObjects(), vyřazení probíhá plynule: WebSockety podporující hibernaci se ve výchozím nastavení hibernují místo uzavření a vyřazení čeká až 30 sekund, než doběhnou probíhající požadavky. Stav v paměti se resetuje ukončením každé instance.
Durable Objects, které neběží nebo jsou nečinné, se přeskakují a namespace se zakázaným vyřazováním z paměti se respektují. Přijímá stejné
DurableObjectEvictionOptionsjakoevictDurableObject().
import { evictAllDurableObjects } from "cloudflare:test"; import { afterEach } from "vitest"; afterEach(async () => { await evictAllDurableObjects(); });
-
D1
-
applyD1Migrations(db:D1Database, migrations:D1Migration[], migrationTableName?:string): Promise<void>- Aplikuje všechny neaplikované Migrace D1 uloženo v
migrationspole do databázedb, přičemž stav migrací zaznamenává domigrationsTableNametabulky.migrationsTableNamemá výchozí hodnotud1_migrations. ZavolejtereadD1Migrations()funkce z@cloudflare/vitest-plugin/configbalíček uvnitř Node.js a získatmigrationspole. Více informací najdete v Recept D1 ↗ pro příklad projektu využívajícího migrace.
- Aplikuje všechny neaplikované Migrace D1 uloženo v
Workflows
-
introspectWorkflowInstance(workflow: Workflow, instanceId: string): Promise<WorkflowInstanceIntrospector>-
Vytvoří introspektor pro konkrétní instanci Workflow, který se používá k upravit jeho chování, await výsledky a vymazat svůj stav během testů. Toto je hlavní vstupní bod pro testování jednotlivých instancí Workflow se známým ID.
import { env } from "cloudflare:workers"; import { introspectWorkflowInstance } from "cloudflare:test"; it("should disable all sleeps, mock an event and complete", async () => { // 1. CONFIGURATION await using instance = await introspectWorkflowInstance(env.MY_WORKFLOW, "123456"); await instance.modify(async (m) => { await m.disableSleeps(); await m.mockEvent({ type: "user-approval", payload: { approved: true, approverId: "user-123" }, }); }); // 2. EXECUTION await env.MY_WORKFLOW.create({ id: "123456" }); // 3. ASSERTION await expect(instance.waitForStatus("complete")).resolves.not.toThrow(); const output = await instance.getOutput(); expect(output).toEqual({ success: true }); // 4. DISPOSE: is implicit and automatic here. }); -
Vrácený
WorkflowInstanceIntrospectorobjekt má následující metody:modify(fn: (m: WorkflowInstanceModifier) => Promise<void>): Promise<void>: Použije úpravy na chování instance Workflow.waitForStepResult(step: { name: string; index?: number }): Promise<unknown>: Počká na dokončení konkrétního kroku a vrátí výsledek. Pokud více kroků sdílí stejný název, použijte nepovinnýindexvlastnost (číslováno od 1, výchozí hodnota1) k zacílení na konkrétní výskyt.waitForStatus(status: InstanceStatus["status"]): Promise<void>: Počká, dokud instance Workflow nedosáhne konkrétního stav (např. 'running', 'complete').getOutput(): Promise<unknown>: Vrátí výstupní hodnotu úspěšně dokončené instance Workflow.getError(): Promise<{name: string, message: string}>: Vrátí informace o chybě neúspěšné instance Workflow. Informace o chybě mají tvar{ name: string; message: string }.dispose(): Promise<void>: Uvolní instanci Workflow, což je klíčové pro izolaci testů. Pokud tato funkce není zavolána aawait usingse nepoužije, izolované úložiště selže a stav instance přetrvá i v následujících testech. Instance, která se v jednom testu dokončí, tak bude dokončená již na začátku testu následujícího.[Symbol.asyncDispose](): Promise<void>: Poskytuje automatické uvolnění (dispose). Je vyvolánoawait usingpříkaz, který voládispose().
-
-
introspectWorkflow(workflow: Workflow): Promise<WorkflowIntrospector>-
Vytvoří introspektor pro Workflow, u kterého nejsou ID instancí předem známa. To umožňuje definovat úpravy, které se použijí na všechny následně vytvořené instance.
import { env, exports } from "cloudflare:workers"; import { introspectWorkflow } from "cloudflare:test"; it("should disable all sleeps, mock an event and complete", async () => { // 1. CONFIGURATION await using introspector = await introspectWorkflow(env.MY_WORKFLOW); await introspector.modifyAll(async (m) => { await m.disableSleeps(); await m.mockEvent({ type: "user-approval", payload: { approved: true, approverId: "user-123" }, }); }); // 2. EXECUTION await env.MY_WORKFLOW.create(); // 3. ASSERTION const instances = introspector.get(); for(const instance of instances) { await expect(instance.waitForStatus("complete")).resolves.not.toThrow(); const output = await instance.getOutput(); expect(output).toEqual({ success: true }); } // 4. DISPOSE: is implicit and automatic here. });Instance workflow nemusí být vytvořena přímo v testu. Introspector zachytí all instance vytvořené po jeho inicializaci. Můžete například vyvolat vytvoření jeden nebo více instance prostřednictvím jediné
fetchudálost do vašeho Workeru:// This also works for the EXECUTION phase: await exports.default.fetch("https://example.com/trigger-workflows"); -
Vrácený
WorkflowIntrospectorobjekt má následující metody:modifyAll(fn: (m: WorkflowInstanceModifier) => Promise<void>): Promise<void>: Použije úpravy na všechny instance Workflow vytvořené po zavoláníintrospectWorkflow.get(): Promise<WorkflowInstanceIntrospector[]>: Vrátí všechnyWorkflowInstanceIntrospectorobjekty z instancí vytvořených pointrospectWorkflowbyla volána.dispose(): Promise<void>: Uvolní Workflow introspector. VšechnyWorkflowInstanceIntrospectorz vytvořených instancí budou také zlikvidovány. To je zásadní pro to, aby úpravy a zachycené instance neunikaly mezi testy. Po zavolání této metodyWorkflowIntrospectorby neměl být znovu použit.[Symbol.asyncDispose](): Promise<void>: Poskytuje automatické uvolnění (dispose). Je vyvolánoawait usingpříkaz, který voládispose().
-
-
WorkflowInstanceModifier-
Tento objekt je poskytován
modifyamodifyAllcallbacky, kterými lze simulovat nebo měnit chování kroků, událostí a sleepů instance Workflow.disableSleeps(steps?: { name: string; index?: number }[]): Vypne sleepy, což způsobístep.sleep()astep.sleepUntil()k okamžitému vyřešení. Pokudstepsje vynecháno, všechna čekání jsou zakázána.disableRetryDelays(steps?: { name: string; index?: number }[]): Vypne prodlevy exponenciálního zpoždění mezi opakováními, takže se opakované pokusy o neúspěšnýstep.do()abyste provedli okamžitě bez čekání. Opakování se stále provádí, jen se odstraní prodleva mezi nimi. Pokudstepsje vynecháno, všechna zpoždění opakování jsou zakázána.mockStepResult(step: { name: string; index?: number }, stepResult: unknown): Simuluje výsledekstep.do(), což způsobí, že okamžitě vrátí zadanou hodnotu, aniž by se provedla implementace daného kroku.mockStepError(step: { name: string; index?: number }, error: Error, times?: number): Vynutístep.do()aby vyvolal chybu, čímž simuluje selhání.timesje volitelné číslo, které určuje, kolikrát má krok skončit chybou. Pokudtimesje vynecháno, krok při každém pokusu selže chybou, což způsobí selhání instance Workflow.forceStepTimeout(step: { name: string; index?: number }, times?: number): Vynutístep.do()aby okamžitě selhal vypršením časového limitu.timesje volitelné číslo, které určuje, kolikrát má u kroku dojít k vypršení časového limitu. Pokudtimesje vynecháno, krok při každém pokusu vyprší časovým limitem, což způsobí selhání instance Workflow.mockEvent(event: { type: string; payload: unknown }): Odešle simulovanou (mock) událost instanci Workflow, což způsobístep.waitForEvent()k vyřešení se zadaným payloadem.typemusí odpovídatwaitForEventtyp.forceEventTimeout(step: { name: string; index?: number }): Vynutístep.waitForEvent()aby okamžitě vypršel časový limit, což způsobí selhání kroku.
import { env } from "cloudflare:workers"; import { introspectWorkflowInstance } from "cloudflare:test"; // This example showcases explicit disposal it("should apply all modifier functions", async () => { // 1. CONFIGURATION const instance = await introspectWorkflowInstance(env.COMPLEX_WORKFLOW, "123456"); try { // Modify instance behavior await instance.modify(async (m) => { // Disables all sleeps to make the test run instantly await m.disableSleeps(); // Disables retry backoff delays so retries execute without waiting await m.disableRetryDelays(); // Mocks the successful result of a data-fetching step await m.mockStepResult( { name: "get-order-details" }, { orderId: "abc-123", amount: 99.99 } ); // Mocks an incoming event to satisfy a `step.waitForEvent()` await m.mockEvent({ type: "user-approval", payload: { approved: true, approverId: "user-123" }, }); // Forces a step to fail once with a specific error to test retry logic await m.mockStepError( { name: "process-payment" }, new Error("Payment gateway timeout"), 1 // Fail only the first time ); // Forces a `step.do()` to time out immediately await m.forceStepTimeout({ name: "notify-shipping-partner" }); // Forces a `step.waitForEvent()` to time out await m.forceEventTimeout({ name: "wait-for-fraud-check" }); }); // 2. EXECUTION await env.COMPLEX_WORKFLOW.create({ id: "123456" }); // 3. ASSERTION expect(await instance.waitForStepResult({ name: "get-order-details" })).toEqual({ orderId: "abc-123", amount: 99.99, }); // Given the forced timeouts, the workflow will end in an errored state await expect(instance.waitForStatus("errored")).resolves.not.toThrow(); const error = await instance.getError(); expect(error.name).toEqual("Error"); expect(error.message).toContain("Execution timed out"); } catch { // 4. DISPOSE await instance.dispose(); } });Chcete-li zacílit na konkrétní krok, použijte jeho
name. Pokud více kroků sdílí stejný název, použijte volitelnýindexvlastnost (číslováno od 1, výchozí hodnota1) k určení výskytu.
-