Jetzt abonnieren, um Benachrichtigungen über neue Beiträge zu erhalten:

Verbesserte Workers-Tests mit Vitest und workerd

2024-03-15

Lesezeit: 10 Min.
Dieser Beitrag ist auch auf English, Español, und Français verfügbar.
Improved Workers testing via Vitest and workerd

Wir freuen uns, heute eine neue Workers-Vitest-Integration bekannt geben zu können, mit der Modul- und Integrationstests über das beliebte Testframework Vitest geschrieben werden können, die direkt in unserer Laufzeitumgebung workerd ausgeführt werden.

Dank dieser Integration können Sie nun alles testen, was mit Ihrem Worker zu tun hat.

Es ist erstmals möglich, Modultests zu schreiben, die in der gleichen Laufzeitumgebung laufen wie Cloudflare Workers im Produktivbetrieb. So haben Sie die Gewissheit, dass Ihr Worker in den Tests das gleiche Verhalten an den Tag legt wie bei der Bereitstellung im Produktivbetrieb. Außerdem können für Workers jetzt Integrationstests geschrieben werden, die durch Cron Triggers zusätzlich zu den traditionellen fetch()-Events ausgelöst werden. Darüber hinaus lassen sich komplexe Anwendungen, die mit KV, R2, D1, Queues, Service Bindings und weiteren Cloudflare-Produkten interagieren, jetzt leichter testen.

Für alle Ihre Tests haben Sie Zugriff auf Vitest-Funktionen wie Snapshots, Mocks, Timer und Spies.

Neben zusätzlichen Tests und Funktionen werden Ihren noch andere Verbesserungen für Entwickler auffallen, etwa das Hot Module Reloading, der standardmäßig aktivierte Überwachungsmodus und die isolierte Speicherung pro Test. Ihre Tests werden während der Entwicklung und Bearbeitung also automatisch neu ausgeführt, ohne dass Sie Ihren Test Runner neu starten müssen.

Workers jetzt mit Vitest testen

Am einfachsten beginnen Sie mit dem Testen Ihrer Workers über Vitest, indem sie über unser Tool create-cloudflare ein neues Workers-Projekts starten:

Wenn Sie diesen Befehl ausführen, wird ein neues Projekt mit der bereits eingerichteten Workers Vitest-Integration per Scaffolding für Sie vorerstellt. Ein Beispiel für einen Modultest und einen Integrationstest ist ebenfalls enthalten.

npm create cloudflare@latest hello-world -- --type=hello-world

Anleitung für die manuelle Installation und Einrichtung

Wenn Sie es vorziehen, die Workers-Vitest-Integration manuell zu installieren und einzurichten, beginnen Sie mit der Installation von @cloudflare/vitest-pool-workers über npm:

@cloudflare/vitest-pool-workers hat eine Peering-Abhängigkeit von einer bestimmten Version von vitest. Moderne Versionen von npm installieren diese automatisch. Wir empfehlen aber, sie auch explizit zu installieren. Die aktuell unterstützte Version finden Sie im Einstiegsleitfaden. Wenn Sie TypeScript verwenden, fügen Sie @cloudflare/vitest-pool-workers zu den types von tsconfig.json hinzu, um Typen für das Modul cloudflare:test zu erhalten:

$ npm install --save-dev @cloudflare/vitest-pool-workers

Aktivieren Sie dann den Pool in Ihrer Vitest-Konfigurationsdatei:

{
  "compilerOptions": {
    "module": "esnext",
    "moduleResolution": "bundler",
    "lib": ["esnext"],
    "types": [
      "@cloudflare/workers-types/experimental",
      "@cloudflare/vitest-pool-workers"
    ]
  }
}

Definieren Sie danach ein Kompatibilitätsdatum nach „2022-10-31“ und aktivieren Sie das nodejs_compat Compatibility Flag bei wrangler.toml:

// vitest.config.js
import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config";

export default defineWorkersConfig({
  test: {
    poolOptions: {
      workers: {
        wrangler: { configPath: "./wrangler.toml" },
      },
    },
  },
});

Alles testen, was von einem Worker exportiert wird

# wrangler.toml
main = "src/index.ts"
compatibility_date = "2024-01-01"
compatibility_flags = ["nodejs_compat"]

Mit der neuen Vitest-Integration von Workers können Sie alles, was von Ihrem Worker exportiert wird, sowohl Modul- als auch Integrationstests unterziehen. Innerhalb dieser Tests können auch miteinander verbundene Ressourcen wie R2, KV und DO getestet werden, ebenso wie eine Anwendung mit mehreren Workers.

Schreiben von Modultests

In einem Workers-Kontext werden im Rahmen eines Modultests Funktionen aus Ihrem Worker importiert und direkt aufgerufen, um dann deren Rückgabewerte zu überprüfen. Nehmen wir an, ihr Worker sieht folgendermaßen aus:

Nachdem Sie die Vitest-Integration von Workers eingerichtet und installiert haben, können Sie diesen Worker testen, indem Sie eine neue Testdatei namens index.spec.js mit dem folgenden Code erstellen:

export function add(a, b) {
  return a + b;
}

export default {
  async fetch(request) {
    const url = new URL(request.url);
    const a = parseInt(url.searchParams.get("a"));
    const b = parseInt(url.searchParams.get("b"));
    return new Response(add(a, b));
  }
}

Mit der Vitest-Integration von Workers können Sie Modultests wie diese für jeden Ihrer Worker schreiben.

import { env, createExecutionContext, waitOnExecutionContext, } from "cloudflare:test";
import { describe, it, expect } from "vitest";
import { add }, worker from "./src";

describe("Hello World worker", () => {
  it(“adds two numbers”, async () => {
    expect(add(2,3).toBe(5);
  });
  it("sends request (unit style)", async () => {
    const request = new Request("http://example.com/?a=3&b=4");
    const ctx = createExecutionContext();
    const response = await worker.fetch(request, env, ctx);
    await waitOnExecutionContext(ctx);
    expect(await response.text()).toMatchInlineSnapshot(`"7"`);
  });
});

Integrationstests schreiben

Modultests eignen sich hervorragend zum Testen einzelner Teile Ihrer Anwendung. Integrationstests ermöglichen demgegenüber die Bewertung mehrerer Funktionskomponenten. So lässt sich sicherstellen, dass Arbeitsabläufe und Funktionen wie erwartet funktionieren. Integrationstext sind in der Regel komplexer als Modultests, bieten aber mehr Sicherheit dahingehend, dass Ihre Anwendung wie erwartet funktioniert. Im Workers-Kontext werden im Rahmen eines Integrationstests HTTP-Anfragen an Ihren Worker geschickt und die HTTP-Antworten überprüft.

Mit der Vitest-Integration von Workers können Integrationstests durchgeführt werden, indem man SELF aus dem neuen cloudflare:test -Dienstprogramm wie folgt importieren:

Wenn Sie SELF für Integrationstests verwenden, läuft Ihr Worker-Quellcode im selben Kontext wie der Test Runner. Das bedeutet, dass Sie Mock-Objekte verwenden können, um Ihren Worker zu kontrollieren.

// test/index.spec.ts
import { SELF } from "cloudflare:test";
import { it, expect } from "vitest";
import "../src";

// an integration test using SELF
it("sends request (integration style)", async () => {
   const response = await SELF.fetch("http://example.com/?a=3&b=4");
   expect(await response.text()).toMatchInlineSnapshot(`"7"`);
});

Testen verschiedener Szenarien

Ob Sie nun Modul- oder Integrationstests schreiben: Wenn Ihre Anwendung Produkte der Entwicklerplattform von Cloudflare (wie KV, R2, D1, Queues oder Durable Objects) verwendet, können Sie diese testen. Um das zu demonstrieren, haben wir eine Reihe von Beispielen erstellt, die Ihnen beim Einstieg in das Testen helfen sollen.

Bessere Testerfahrung === bessere Tests

Bessere Testwerkzeuge machen es einfacher, Projekte von Anfang an zu testen, was zu einer höheren Gesamtqualität und einer positiveren Endnutzererfahrung führt. Die Vitest-Integration von Workers sorgt für ein besseres Erlebnis – einerseits für die Entwickler, aber auch, weil sie das Testen einer gesamten Anwendung erleichtert.

Der Rest dieses Blog-Beitrags wird sich darauf konzentrieren, wie wir diese neue Testintegration aufgebaut haben. Wir tauchen dazu tiefer in die Funktionsweise von Vitest ein und erörtern die Probleme, auf die wir bei dem Versuch gestoßen sind, ein Framework in unsere Laufzeitumgebung einzubinden. Schließlich gehen wir noch darauf ein, wie wir diese Probleme gelöst haben und wie sich die Entwicklererfahrung dadurch verbessert.

Traditionelle Funktionsweise von Vitest

Beim Starten der CLI von Vitest sammelt diese zunächst alle Ihre Testdateien und ordnet sie an. Standardmäßig verwendet Vitest einen „Threads“-Pool, der Node.js-Worker-Threads zur Isolierung und parallelen Ausführung von Tests erzeugt. Jeder Thread erhält eine auszuführende Testdatei, fordert bei Bedarf dynamisch Quellcode an und wertet ihn aus. Importiert der Test Runner ein Modul, sendet er eine Anfrage an den „Vite Node Server“ des Hosts, der als Antwort entweder von Vite transformierten JavaScript-Rohcode oder einen externen Modulpfad ausgibt. Wird Rohcode ausgegeben, wird er mit der Funktion node:vm runInThisContext() ausgeführt. Wird ein Modulpfad ausgegeben, wird er mit dynamic import() importiert. Die Umwandlung von Nutzerquellcode mit Vite ermöglicht Hot Module Reloading (HMR): Ändert sich ein Modul, wird es im Modul-Cache ungültig gemacht und beim nächsten Import eine neue Version als Antwort ausgegeben.

Miniflare ist ein vollständig lokaler Simulator für die Entwicklerplattform von Cloudflare. Miniflare v2 bot eine benutzerdefinierte Umgebung für Vitest, die es Ihnen ermöglichte, Ihre Tests innerhalb der Workers-Sandbox auszuführen. So konnten Sie mithilfe der Laufzeitumgebungs-API von Workers jede Funktion in Ihre Tests importieren und aufrufen. Sie waren nicht auf Integrationstests beschränkt, die lediglich HTTP-Anfragen senden und empfangen. Darüber hinaus bot diese Umgebung eine isolierte Speicherung pro Test, wobei alle Änderungen am Ende jedes Tests automatisch rückgängig gemacht wurden. In Miniflare v2 war diese Umgebung relativ einfach zu implementieren. Wir hatten die Laufzeitumgebungs-API von Workers bereits in einer Node.js-Umgebung reimplementiert und konnten sie mithilfe von Vitest-API in den globalen Bereich des Test Runners injizieren.

overview of Vitest’s architecture using Miniflare v2’s environments

Im Gegensatz dazu führt Miniflare v3 Ihren Worker-Quellcode innerhalb der gleichen workerd-Laufzeitumgebung aus, die von Cloudflare im Produktivbetrieb verwendet wird. Die Ausführung von Tests direkt in workerd stellte eine Herausforderung dar – workerd läuft in einem eigenen Prozess, getrennt vom Node.js Worker-Thread. Es ist nicht möglich, JavaScript-Klassen über eine Prozessgrenze hinweg zu referenzieren.

Lösung des Problems mit benutzerdefinierten Pools

problem with Miniflare v3, the runtime APIs are defined in a separate process to the test environments, and JavaScript objects cannot cross process boundaries

Stattdessen verwenden wir die Funktion der benutzerdefinierten Pools von Vitest, um den lokal mit workerd laufenden Test Runner in Cloudflare Workers auszuführen. Ein Pool erhält die auszuführenden Testdateien und entscheidet, wie sie ausgeführt werden sollen. Durch die Ausführung des Runners innerhalb von workerd haben die Tests direkten Zugriff auf die Laufzeitumgebungs-API von Workers, da sie in einem Worker ausgeführt werden. WebSockets werden verwendet, um serialisierbare RPC-Nachrichten zwischen dem Node.js-Host und dem workerd-Prozess zu senden und zu empfangen. Beachten Sie, dass wir hier genau denselben Test Runner-Quellcode ausführen, der ursprünglich für einen Node-Kontext innerhalb eines Workers entwickelt wurde. Das bedeutet, dass unser Worker die in Node integrierten Module bereitstellen, dynamische Code-Evaluierung unterstützen und beliebige Module von der Festplatte mit Node-Auflösungsverhalten laden muss. Das nodejs_compat Compatibility Flag unterstützt einige der integrierten Module von Node, löst aber unsere anderen Probleme nicht. Dafür mussten uns etwas einfallen lassen …

Dynamische Quellcode-Bewertung

our solution for Miniflare v3, make the tests run in workerd, and use WebSockets for communication

Aus Sicherheitsgründen erlaubt die Cloudflare Workers-Laufzeitumgebung keine dynamische Code-Evaluierung mittels`eval()` oder `new Function()`. Außerdem müssen alle Module im Voraus definiert werden, bevor die Ausführung beginnt. Ohne Aufhebung dieser Beschränkungen haben wir keine Möglichkeit, den von Vite transformierten JavaScript-Rohcode auszuführen oder beliebige Module von der Festplatte zu importieren. Glücklicherweise ist das Sicherheitsmodell für Quellcode, der nur lokal ausgeführt werden soll – etwa für Tests – viel laxer als für implementierten Quellcode. Um die Unterstützung lokaler Tests und anderer entwicklungsspezifischer Anwendungsfälle wie die neue Laufzeitumgebungs-API von Vite zu ermöglichen, haben wir `workerd` um „unsafe-eval bindings“ und „module-fallback services“ erweitert.

Unsafe-eval-Bindungen bieten lokalen Zugriff auf die Funktion eval() und die Konstruktoren new Function()/new AsyncFunction()/new WebAssembly.Module(). Indem wir diese über eine Bindung bereitstellen, behalten wir die Kontrolle darüber, welcher Quellcode Zugriff auf diese Funktionen hat.

Mithilfe der Methode eval() der unsafe-eval-Bindung konnten wir ein Polyfill für die erforderliche vm.runInThisContext()-Funktion implementieren. Wir könnten das Laden beliebiger Module von der Festplatte zwar auch mithilfe von unsafe-eval-Bindungen implementieren, das würde aber erfordern, dass wir das Modulauflösungssystem von workerd in JavaScript nachbilden. Stattdessen erlauben wir es, dass Worker mit Modul-Fallback-Diensten konfiguriert werden. Sind diese aktiviert, werden Importe, die nicht von workerd aufgelöst werden können, zu HTTP-Anfragen an den Fallback-Dienst. Diese beinhalten den Specifier und den Referrer. Außerdem geben sie an, ob es sich um import oder require handelte. Der Dienst kann mit einer Moduldefinition oder einem Redirect zu einem anderen Ort antworten, wenn der aufgelöste Ort nicht mit dem Specifier übereinstimmt. Anfragen, die von synchronen require stammen, blockieren den Hauptthread, bis das Modul aufgelöst ist. Der Fallback-Dienst des Vitest-Pools von Workers implementiert eine Node-ähnliche Auflösung mit Node-ähnlicher Interoperabilität zwischen CommonJS- und ES-Modulen.

// Type signature for unsafe-eval bindings
interface UnsafeEval {
  eval(script: string, name?: string): unknown;
  newFunction(script: string, name?: string, ...args: string[]): Function;
  newAsyncFunction(script: string, name?: string, ...args: string[]): AsyncFunction;
  newWasmModule(src: BufferSource): WebAssembly.Module;
}

Durable Objects als Test Runner

Wir können nun beliebigen Quellcode ausführen und importieren. Der nächste Schritt besteht darin, den Thread-Worker von Vitest innerhalb von workerd zum Laufen zu bringen. Jede eingehende Anfrage hat ihren eigenen Anfragekontext. Um die Performance insgesamt zu verbessern, können E/A-Objekte wie Streams, Request/Response-Bodys und WebSockets, die in einem Anfragekontext erstellt wurden, nicht von einem anderen verwendet werden. Wenn wir einen WebSocket für RPC zwischen dem Pool und unseren workerd-Prozessen verwenden wollen, müssen wir also sicherstellen, dass der WebSocket nur von einem Anfragekontext aus verwendet wird. Um dies zu koordinieren, definieren wir ein Durable Object als Singleton, das die RPC-Verbindung annimmt und von dem aus die Tests laufen. Funktionen, die RPC verwenden, wie das Auflösen von Modulen, das Melden von Ergebnissen und die Konsolenprotokollierung, werden immer dieses Singleton verwenden. Wir benutzen das „magic proxy“-System von Miniflare, um einen Verweis auf den Stub des Singletons in Node.js zu erhalten und eine WebSocket-Upgrade-Anfrage direkt an dieses zu senden. Nach dem Hinzufügen einiger weiterer Node.js-Polyfills und eines cloudflare:test-Basismoduls, das den Zugriff auf Bindungen und eine Funktion zum Erstellen von ExecutionContext ermöglicht, können wir einfache Workers-Modultests schreiben 🎉.

Integrationstests mit Hot Module Reloading

architecture of the Workers Vitest Pool

Neben Modultests werden auch Integrationstests mit einer speziellen SELF-Dienstbindung im Modul cloudflare:test unterstützt. Dies verweist auf einen speziellen export default { fetch(...) {...} }-Handler, der Vite benutzt, um Ihr main-Workers-Modul zu importieren.

Die Verwendung der Transformationspipeline von Vite bedeutet, dass Ihrem Handler Hot Module Reloading (HMR) umsonst zur Verfügung steht. Wird der Quellcode aktualisiert, wird der Modul-Cache ungültig gemacht. Außerdem werden die Tests erneut durchgeführt und nachfolgende Anfragen werden mit dem neuen Quellcode ausgeführt. Der gleiche Ansatz der Verpackung von Nutzerquellcode-Handlern wird auch auf Durable Objects angewandt und bietet die gleichen HMR-Vorteile.

Integrationstests können durch den Aufruf von SELF.fetch() geschrieben werden, der ein fetch()-Ereignis an Ihren Nutzerquellcode im gleichen globalen Bereich wie Ihr Test, aber in einem anderen Anforderungskontext sendet. Das bedeutet, dass globale Mock-Objekte für die Ausführung Ihrer Worker gelten, ebenso wie Einschränkungen der Lebensdauer des Anfragekontexts. Insbesondere, wenn Sie vergessen, ctx.waitUntil() aufzurufen, werden Sie eine entsprechende Fehlermeldung erhalten. Das wäre nicht der Fall, wenn Sie den Handler Ihres Workers direkt in einem Modultest aufrufen würden, da Sie ihn dann unter dem Durable Object-Anforderungskontext des Runner-Singletons betreiben würden, dessen Lebensdauer automatisch verlängert wird.

Isolierte Speicherung pro Test

// test/index.spec.ts
import { SELF } from "cloudflare:test";
import { it, expect } from "vitest";
import "../src/index";

it("sends request", async () => {
   const response = await SELF.fetch("https://example.com");
   expect(await response.text()).toMatchInlineSnapshot(`"body"`);
});

Die meisten Workers-Anwendungen haben mindestens eine Bindung an einen Cloudflare-Speicherdienst wie KV, R2 oder D1. Im Idealfall sollten die Tests in sich abgeschlossen sein und in beliebiger Reihenfolge oder einzeln ausgeführt werden können. Um das zu ermöglichen, müssen die Schreibvorgänge im Speicher am Ende jedes Tests rückgängig gemacht werden, damit die Lesevorgänge anderer Tests nicht beeinträchtigt werden. Das ist zwar auch manuell möglich, aber es kann schwierig sein, den Überblick über alle Schreibvorgänge zu behalten und sie in der richtigen Reihenfolge rückgängig zu machen. Nehmen wir zum Beispiel die folgenden zwei Funktionen:

Wenn wir diese Funktionen testen wollten, könnte der Quellcode dazu ungefähr so aussehen im folgenden Beispiel. Wir müssen alle Schlüssel, in die wir schreiben könnten, im Blick behalten und ihre Werte am Ende von Tests wiederherstellen – selbst wenn diese Tests fehlschlagen.

// helpers.ts
interface Env {
  NAMESPACE: KVNamespace;
}
// Get the current list stored in a KV namespace
export async function get(env: Env, key: string): Promise<string[]> {
  return await env.NAMESPACE.get(key, "json") ?? [];
}
// Add an item to the end of the list
export async function append(env: Env, key: string, item: string) {
  const value = await get(env, key);
  value.push(item);
  await env.NAMESPACE.put(key, JSON.stringify(value));
}

Mit dem kürzlich eingeführten onTestFinished()-Hook lässt sich dies etwas leichter bewerkstellen. Sie müssen sich aber immer noch merken, in welche Schlüssel geschrieben wurde, oder sie am Anfang/Ende von Tests aufzählen. Sie müssen dies auch für KV, R2, Durable Objects, Caches und jeden anderen von ihnen verwendeten Speicherdienst verwalten. Idealerweise sollte das Testframework diese Verwaltung für Sie übernehmen …

// helpers.spec.ts
import { env } from "cloudflare:test";
import { beforeAll, beforeEach, afterEach, it, expect } from "vitest";
import { get, append } from "./helpers";

let startingList1: string | null;
let startingList2: string | null;
beforeEach(async () => {
  // Store values before each test
  startingList1 = await env.NAMESPACE.get("list 1");
  startingList2 = await env.NAMESPACE.get("list 2");
});
afterEach(async () => {
  // Restore starting values after each test
  if (startingList1 === null) {
    await env.NAMESPACE.delete("list 1");
  } else {
    await env.NAMESPACE.put("list 1", startingList1);
  }
  if (startingList2 === null) {
    await env.NAMESPACE.delete("list 2");
  } else {
    await env.NAMESPACE.put("list 2", startingList2);
  }
});

beforeAll(async () => {
  await append(env, "list 1", "one");
});

it("appends to one list", async () => {
  await append(env, "list 1", "two");
  expect(await get(env, "list 1")).toStrictEqual(["one", "two"]);
});

it("appends to two lists", async () => {
  await append(env, "list 1", "three");
  await append(env, "list 2", "four");
  expect(await get(env, "list 1")).toStrictEqual(["one", "three"]);
  expect(await get(env, "list 2")).toStrictEqual(["four"]);
});

Genau das tut der Vitest-Pool von Workers mit der Option isolatedStorage, die standardmäßig aktiviert ist. Alle in einem Test durchgeführten Schreibvorgänge auf den Speicher werden am Ende des Tests automatisch rückgängig gemacht. Um Daten in beforeAll()-Hooks zu setzen, einschließlich solcher in verschachtelten describe()-Blöcken, wird ein Stack verwendet. Vor jeder Suite oder jedem Test wird ein neuer Frame in den Speicher-Stack eingefügt. Alle im Rahmen des Tests oder durch die zugehörigen beforeEach()/afterEach()-Hooks durchgeführten Schreibvorgänge werden in den Frame geschrieben. Nach jeder Suite oder jedem Test wird der oberste Frame aus dem Speicher-Stack gelöst und alle Schreibvorgänge werden rückgängig gemacht.

Miniflare implementiert Simulatoren für Speicherdienste auf Grundlage von Durable Objects mit einem separaten Blob-Speicher. Wenn workerd lokal läuft, wird SQLite zur Speicherung von Durable Objects verwendet. Zur Implementierung eines isolierten Speichers implementieren wir einen Stack von .sqlite-Datenbankdateien auf der Festplatte, indem wir die Datenbanken beim „Pushing“ sichern und beim „Popping“ (Loslösen des Frames) wiederherstellen. Die im separaten Speicher abgelegten Blobs werden durch Stack-Operationen beibehalten und am Ende jedes Testlaufs bereinigt. Das funktioniert zwar, erfordert aber das Kopieren vieler .sqlite-Dateien. Wir möchten als Nächstes die Verwendung von SAVEPOINTS von SQLite für eine effizientere Lösung ausloten.

Storage stack frames created with isolated storage enabled

Deklaratives Request Mocking

Zusätzlich zur Speicherung werden die meisten Workers ausgehende fetch()-Anfragen stellen. Für Tests ist es oft nützlich, die Antworten auf diese Anfragen zu simulieren. Miniflare erlaubt es Ihnen bereits, einen undici MockAgent zu spezifizieren, durch den alle Anfragen geleitet werden. Die Klasse MockAgent bietet eine deklarative Schnittstelle, um die zu spiegelnden Anfragen und die entsprechenden Antworten zu spezifizieren. Diese API ist relativ einfach gehalten, aber trotzdem flexibel genug für fortgeschrittene Anwendungsfälle. Wir stellen eine Instanz von MockAgent als fetchMock im Modul cloudflare:test zur Verfügung.

Um das zu implementieren, haben wir eine abgespeckte Version von undici gebündelt, die nur den MockAgent-Quellcode enthält. Dann haben wir einen benutzerdefinierten undici Dispatcher erstellt, der die globale `fetch()`-Funktion von Workers anstelle der integrierten HTTP-Implementierung von undici auf Grundlage von llhttp und node:net verwendet.

import { fetchMock } from "cloudflare:test";
import { beforeAll, afterEach, it, expect } from "vitest";

beforeAll(() => {
  // Enable outbound request mocking...
  fetchMock.activate();
  // ...and throw errors if an outbound request isn't mocked
  fetchMock.disableNetConnect();
});
// Ensure we matched every mock we defined
afterEach(() => fetchMock.assertNoPendingInterceptors());

it("mocks requests", async () => {
  // Mock the first request to `https://example.com`
  fetchMock
    .get("https://example.com")
    .intercept({ path: "/" })
    .reply(200, "body");

  const response = await fetch("https://example.com/");
  expect(await response.text()).toBe("body");
});

Durable Objects direkt testen

Die benutzerdefinierte Vitest-Umgebung von Miniflare v2 unterstützt den direkten Zugriff auf die Instanzmethoden und den Zustand von Durable Objects in Tests. Das ermöglichte es Ihnen, Durable Objects wie jede andere JavaScript-Klasse Modultests zu unterziehen – Sie konnten bestimmte Methoden und Eigenschaften nachahmen oder bestimmte Handler wie alarm() sofort aufrufen. Um dies in workerd zu implementieren, stützen wir uns auf unser bestehendes Wrapping von Nutzer-Durable Objects für Vite-Transformationen und das Hot Module Reloading. Wenn Sie die Funktion runInDurableObject(stub, callback) von cloudflare:test aufrufen, speichern wir `callback` in einem globalen Cache. Außerdem senden wir eine spezielle fetch()-Anfrage an stub, die von dem Wrapper abgefangen wird. Der Wrapper führt den callback im Anfragekontext des Durable Object aus und speichert das Ergebnis im gleichen Cache. runInDurableObject() liest dann aus diesem Cache und gibt das Ergebnis zurück.

Dies setzt voraus, dass das Durable Object in demselben isolierten Bereich läuft wie der Aufruf runInDurableObject(). Während das für lokal laufende Durable Objects desselben Workers gilt, kann auf Durable Objects, die in Hilfs-Workern definiert sind, folglich nicht direkt zugegriffen werden.

Probieren Sie es aus!

Wir freuen uns, das Paket @cloudflare/vitest-pool-workers auf npm veröffentlichen und Ihnen eine verbesserte Testerfahrung bieten zu können.

Lesen Sie unbedingt den Leitfaden „Write your first test“ und beginnen Sie direkt damit, Modul- und Integrationstests zu schreiben. Wenn Sie bisher Tests mit einer unserer früheren Optionen geschrieben haben, sollte unser Migrationsleitfaden für unstable_dev oder unser Migrationsleitfaden für Miniflare 2 die wichtigsten Unterschiede erklären und Ihnen dabei helfen, Ihre Tests schnell umzustellen.

Wenn Sie auf Probleme stoßen oder Verbesserungsvorschläge haben, melden Sie dies bitte in unserem GitHub-Repository oder kontaktieren Sie uns über unseren Discord-Kanal für Entwickler.

Wir schützen komplette Firmennetzwerke, helfen Kunden dabei, Internetanwendungen effizient zu erstellen, jede Website oder Internetanwendung zu beschleunigen, DDoS-Angriffe abzuwehren, Hacker in Schach zu halten, und unterstützen Sie bei Ihrer Umstellung auf Zero Trust.

Greifen Sie von einem beliebigen Gerät auf 1.1.1.1 zu und nutzen Sie unsere kostenlose App, die Ihr Internet schneller und sicherer macht.

Wenn Sie mehr über unsere Mission, das Internet besser zu machen, erfahren möchten, beginnen Sie hier. Sie möchten sich beruflich neu orientieren? Dann werfen Sie doch einen Blick auf unsere offenen Stellen.
Cloudflare WorkersEntwicklerTesting (DE)

Folgen auf X

Brendan Coll|@_mrbbot
Adam Murray|@admah
Cloudflare|@cloudflare

Verwandte Beiträge