惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google DeepMind News
Google DeepMind News
S
Security Affairs
阮一峰的网络日志
阮一峰的网络日志
L
LangChain Blog
Microsoft Azure Blog
Microsoft Azure Blog
雷峰网
雷峰网
Recent Announcements
Recent Announcements
WordPress大学
WordPress大学
The GitHub Blog
The GitHub Blog
博客园_首页
The Cloudflare Blog
M
MIT News - Artificial intelligence
博客园 - 【当耐特】
MyScale Blog
MyScale Blog
S
SegmentFault 最新的问题
P
Proofpoint News Feed
Y
Y Combinator Blog
Jina AI
Jina AI
博客园 - 聂微东
A
About on SuperTechFans
Blog — PlanetScale
Blog — PlanetScale
博客园 - 司徒正美
G
Google Developers Blog
云风的 BLOG
云风的 BLOG
F
Full Disclosure
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Microsoft Security Blog
Microsoft Security Blog
爱范儿
爱范儿
T
Tailwind CSS Blog
J
Java Code Geeks
Vercel News
Vercel News
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Stack Overflow Blog
Stack Overflow Blog
罗磊的独立博客
小众软件
小众软件
酷 壳 – CoolShell
酷 壳 – CoolShell
T
The Blog of Author Tim Ferriss
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
博客园 - 三生石上(FineUI控件)
W
WeLiveSecurity
PCI Perspectives
PCI Perspectives
Attack and Defense Labs
Attack and Defense Labs
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
宝玉的分享
宝玉的分享
IT之家
IT之家
Hacker News: Ask HN
Hacker News: Ask HN
The Register - Security
The Register - Security
T
The Exploit Database - CXSecurity.com
T
Threat Research - Cisco Blogs

Workflow SDK Documentation

Patterns for Defining Tools Human-in-the-Loop Building Durable AI Agents Queueing User Messages Resumable Streams Sleep, Suspense, and Scheduling Streaming Updates from Tools API Reference Workflow Globals Changelog Resilient run start Cookbook Building a World Deploying Astro Express Fastify Hono Getting Started NestJS Next.js Nitro Nuxt Python SvelteKit Vite corrupted-event-log fetch-in-workflow hook-conflict Errors node-js-module-in-workflow serialization-failed start-invalid-workflow-function step-not-registered timeout-in-workflow webhook-invalid-respond-with-value webhook-response-not-sent workflow-not-registered Errors & Retrying Hooks & Webhooks Idempotency Foundations Serialization Starting Workflows Streaming Versioning Workflows and Steps How the Directives Work Encryption Event Sourcing Framework Integrations Understanding Directives Migration Guides Migrating from AWS Step Functions Migrating from Inngest Migrating from Temporal Observability Testing createHook createWebhook defineHook FatalError fetch getStepMetadata getWorkflowMetadata getWritable workflow RetryableError sleep @workflow/vitest DurableAgent @workflow/ai WorkflowChatTransport getHookByToken getRun getWorld workflow/api resumeHook resumeWebhook Chat Session Modeling runtime-decryption-failed Upgrading Workflows abort-signal-timeout-in-workflow Cancellation How Cancellation Works Internal Serializable AbortController and AbortSignal Eager Processing of Steps & Incremental Event Replay TanStack Start Agent Cancellation Sequential & Parallel Execution Workflow Composition Local World | Workflow SDK Postgres World | Workflow SDK Vercel World | Workflow SDK Migrating from trigger.dev Secure Credential Handling Local World | Workflow SDK Postgres World | Workflow SDK Vercel World | Workflow SDK
Server-Based Testing
2026-05-31 · via Workflow SDK Documentation

Integration test workflows against a running server when you need to test the full HTTP layer.

The Vitest plugin runs workflows entirely in-process and is the recommended approach for most testing scenarios. However, there are cases where you may want to test against a running server:

  • Testing the full HTTP layer (middleware, authentication, request handling)
  • Reproducing behavior that only occurs in a specific framework's runtime (e.g. Next.js, Nitro)
  • Testing webhook endpoints that receive real HTTP requests

This guide shows how to set up integration tests that spawn a dev server as a sidecar process. The example below uses Nitro, but the same pattern works with any supported server framework. It is meant as a starting point — customize the server setup to match your own deployment environment.

Create a Vitest config with the workflow() Vite plugin for code transforms and a globalSetup script that manages the server lifecycle:

import { defineConfig } from "vitest/config";
import { workflow } from "workflow/vite"; 

export default defineConfig({
  plugins: [workflow()], 
  test: {
    include: ["**/*.server.test.ts"],
    testTimeout: 60_000,
    globalSetup: "./vitest.server.setup.ts", 
    env: {
      WORKFLOW_LOCAL_BASE_URL: "http://localhost:4000", 
    },
  },
});

Note the import path: workflow/vite (not @workflow/vitest). The Vite plugin handles code transforms but does not set up in-process execution. The server handles workflow execution instead.

The globalSetup script starts a dev server before tests run and tears it down afterwards. This example uses Nitro, but you can use any server framework that supports the workflow runtime.

import { spawn } from "node:child_process";
import { setTimeout as delay } from "node:timers/promises";
import type { ChildProcess } from "node:child_process";

let server: ChildProcess | null = null;
const PORT = "4000";

function emitSetupLog(event: string, fields: Record<string, unknown> = {}) {
  console.log(
    JSON.stringify({
      scope: "workflow-server-test",
      event,
      port: PORT,
      ...fields,
    })
  );
}

export async function setup() { 
  const stdout: string[] = [];
  const stderr: string[] = [];

  emitSetupLog("server_starting", {
    command: `npx nitro dev --port ${PORT}`,
  });

  server = spawn("npx", ["nitro", "dev", "--port", PORT], {
    stdio: "pipe",
    detached: false,
    env: process.env,
  });

  const ready = await new Promise<boolean>((resolve) => {
    const timeout = setTimeout(() => resolve(false), 15_000);

    server?.stdout?.on("data", (data) => {
      const output = data.toString();
      stdout.push(output);
      emitSetupLog("server_stdout", { message: output.trim() });

      if (output.includes("listening") || output.includes("ready")) {
        clearTimeout(timeout);
        resolve(true);
      }
    });

    server?.stderr?.on("data", (data) => {
      const output = data.toString();
      stderr.push(output);
      emitSetupLog("server_stderr", { message: output.trim() });
    });

    server?.on("error", (error) => {
      emitSetupLog("server_process_error", {
        name: error.name,
        message: error.message,
      });
      clearTimeout(timeout);
      resolve(false);
    });

    server?.on("exit", (code, signal) => {
      emitSetupLog("server_exit", { code, signal });
    });
  });

  if (!ready) {
    const recentStdout = stdout.join("").trim().slice(-2000);
    const recentStderr = stderr.join("").trim().slice(-2000);

    throw new Error(
      [
        "Server failed to start within 15 seconds.",
        `Command: npx nitro dev --port ${PORT}`,
        `WORKFLOW_LOCAL_BASE_URL: http://localhost:${PORT}`,
        `Recent stdout:\n${recentStdout || "(empty)"}`,
        `Recent stderr:\n${recentStderr || "(empty)"}`,
      ].join("\n\n")
    );
  }

  await delay(2_000);

  process.env.WORKFLOW_LOCAL_BASE_URL = `http://localhost:${PORT}`; 

  emitSetupLog("server_ready", {
    baseUrl: process.env.WORKFLOW_LOCAL_BASE_URL,
  });
}

export async function teardown() { 
  if (!server) return;

  emitSetupLog("server_stopping");

  server.kill("SIGTERM");
  await delay(1_000);

  if (!server.killed) {
    emitSetupLog("server_force_kill");
    server.kill("SIGKILL");
  }
}

These JSON log lines are intentional. They give CI jobs, local tooling, and agents stable events to watch for (server_starting, server_stdout, server_stderr, server_ready, server_exit), and the thrown timeout error includes the command, expected WORKFLOW_LOCAL_BASE_URL, and buffered stdout/stderr so a failed setup is actionable without interactive debugging.

The setup script sets WORKFLOW_LOCAL_BASE_URL so the workflow runtime sends step execution requests to the running server.

You can use any server framework that supports the workflow runtime. The example above uses Nitro, but you could also use Next.js, Hono, or any other supported server.

Tests are written the same way as in-process integration tests. You can use the same programmatic APIs — start(), resumeHook(), resumeWebhook(), and getRun().wakeUp() — to control workflow execution:

import { describe, it, expect } from "vitest";
import { start, getRun, resumeHook } from "workflow/api";
import { calculateWorkflow } from "./calculate";
import { approvalWorkflow } from "./approval";

describe("calculateWorkflow", () => {
  it("should compute the correct result", async () => {
    const run = await start(calculateWorkflow, [2, 7]);
    const result = await run.returnValue;

    expect(result).toEqual({
      sum: 9,
      product: 14,
      combined: 23,
    });
  });
});

describe("approvalWorkflow", () => {
  it("should publish when approved", async () => {
    const run = await start(approvalWorkflow, ["doc-1"]);

    // Use resumeHook and wakeUp to control workflow execution
    await resumeHook("approval:doc-1", {
      approved: true,
      reviewer: "alice",
    });

    await getRun(run.runId).wakeUp();

    const result = await run.returnValue;
    expect(result).toEqual({
      status: "published",
      reviewer: "alice",
    });
  });
});

In server-based tests, the waitForSleep() and waitForHook() helpers from @workflow/vitest are not available since there is no in-process world. Instead, use the programmatic APIs directly — you may need to add short delays or polling to ensure the workflow has reached the desired state before resuming.

Add a script to your package.json:

{
  "scripts": {
    "test": "vitest",
    "test:server": "vitest --config vitest.server.config.ts"
  }
}
ScenarioRecommended approach
Testing workflow logic, steps, hooks, retriesIn-process plugin
Testing HTTP middleware or authenticationServer-based
Testing webhook endpoints with real HTTPServer-based
CI/CD pipeline testingIn-process plugin
Reproducing framework-specific behaviorServer-based