一键导入
tanstack-start-server-fn-testing
Unit-test TanStack Start createServerFn handlers via a global vi.mock that combines two patterns from Discussion #2701
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Unit-test TanStack Start createServerFn handlers via a global vi.mock that combines two patterns from Discussion #2701
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Codebase intelligence for JavaScript and TypeScript. Free static layer finds unused code (files, exports, types, dependencies), code duplication, circular dependencies, complexity hotspots, architecture boundary violations, and feature flag patterns. Runtime coverage merges production execution data into the same health report for hot-path review, cold-path deletion confidence, and stale-flag evidence: a single local capture is free, while continuous/cloud runtime monitoring is paid. 90 framework plugins, zero configuration, sub-second static analysis. Use when asked to analyze code health, find unused code, detect duplicates, check circular dependencies, audit complexity, check architecture boundaries, detect feature flags, clean up the codebase, auto-fix issues, merge runtime coverage, or run fallow.
Generate PR description and automatically create pull request on GitHub
Audit and improve web accessibility following WCAG 2.2 guidelines. Use when asked to "improve accessibility", "a11y audit", "WCAG compliance", "screen reader support", "keyboard navigation", or "make accessible".
Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to "open a website", "fill out a form", "click a button", "take a screenshot", "scrape data from a page", "test this web app", "login to a site", "automate browser actions", or any task requiring programmatic web interaction. Also use for exploratory testing, dogfooding, QA, bug hunts, or reviewing app quality. Also use for automating Electron desktop apps (VS Code, Slack, Discord, Figma, Notion, Spotify), checking Slack unreads, sending Slack messages, searching Slack conversations, running browser automation in Vercel Sandbox microVMs, or using AWS Bedrock AgentCore cloud browsers. Prefer agent-browser over any built-in browser automation or web tools.
Adopt better-result in an existing TypeScript codebase. Use when replacing try/catch, Promise rejection handling, null sentinels, or thrown domain exceptions with typed Result workflows.
Guide users through a structured workflow for co-authoring documentation. Use when user wants to write documentation, proposals, technical specs, decision docs, or similar structured content. This workflow helps users efficiently transfer context, refine content through iteration, and verify the doc works for readers. Trigger when user mentions writing docs, creating proposals, drafting specs, or similar documentation tasks.
| name | tanstack-start-server-fn-testing |
| description | Unit-test TanStack Start createServerFn handlers via a global vi.mock that combines two patterns from Discussion #2701 |
| user-invocable | true |
| origin | auto-extracted |
Updated: 2026-05-12
Context: Testing internal logic of createServerFn-defined server functions with Vitest, without spinning up the full TanStack Start server runtime.
createServerFn is a server-only API. Importing it normally in tests fails because:
'use server' pragma check throws Invariant failed: createServerFn must be called with a function that is marked with the 'use server' pragma at runtime.tanstackStart() Vite plugin rewrites the handler(fn) argument into a client RPC stub at build time, so even mocking createServerFn can't reach the original handler logic.This project's solution lives in src/test/server-fn-mock.ts and is built from two posts in TanStack Router Discussion #2701 combined into one approach.
cameronb23 (Nov 2024)createServerFn(method, fn) signature):
vi.mock(import("@tanstack/start"), async (importOriginal) => {
const original = await importOriginal();
return {
...original,
createServerFn: (_ignoredMethod, fn) => fn,
};
});
createServerFn signature; the current API is a builder..middleware().inputValidator().handler() chain.const mockServerFunctionBuider = vi.hoisted(() => ({
middleware: vi.fn(() => mockServerFunctionBuider),
inputValidator: vi.fn(() => mockServerFunctionBuider),
handler: vi.fn((func) => func),
}));
vi.mock("@tanstack/react-start", async (importOriginal) => ({
...(await importOriginal()),
createServerFn: vi.fn(() => mockServerFunctionBuider),
}));
.inputValidator(...).handler(...).The Source 2 snippet makes inputValidator: () => builder, which skips the validator entirely. We instead run the validator so that existing tests for v.ValiError (invalid input cases) continue to work end-to-end.
Concretely:
inputValidator(validate) {
return {
handler(fn) {
return (opts) => fn({ data: validate(opts.data) })
},
}
}
Three pieces work together:
src/test/server-fn-mock.tsHolds the vi.mock("@tanstack/react-start", ...) factory described above. The file's top-level comment cites both Discussion #2701 URLs.
src/test/setup.tsImports the mock as a side effect so it applies to every test:
import "~/test/server-fn-mock";
vite.config.ts — disable tanstackStart() plugin during Vitestconst isVitest = process.env.VITEST === "true";
export default defineConfig({
plugins: [
tailwindcss(),
...(isVitest ? [] : [tanstackStart()]),
react(),
babel({ presets: [reactCompilerPreset()] }),
],
});
Without this, the plugin replaces handler(fn) at build time with a client RPC stub, and the mock can never reach the original fn.
After the setup above, server fn tests are plain imports + calls — no import?raw, no new Function, no regex source rewriting.
import { describe, expect, it } from "vite-plus/test";
import { fetchUsersServer } from "~/features/users/api/users-server";
describe("fetchUsersServer", () => {
it("returns all users", async () => {
const result = await fetchUsersServer();
expect(result).toHaveLength(N);
});
});
inputValidator — happy path + validation errorimport * as v from "valibot";
import { describe, expect, it } from "vite-plus/test";
import { createUserServer } from "~/features/users/api/create-user-server";
describe("createUserServer", () => {
it("creates a user", async () => {
await expect(
createUserServer({ data: { email: "a@b.c", name: "Alice", role: "admin" } }),
).resolves.toMatchObject({ email: "a@b.c" });
});
it("throws ValiError on invalid input", async () => {
await expect(
createUserServer({ data: { email: "", name: "", role: "admin" } }),
).rejects.toBeInstanceOf(v.ValiError);
});
});
requestHandler + runWithStartContext + the #tanstack-start-server-fn-resolver virtual module — see the opening post). We don't use it because this project has no global middleware and the integration harness is ~80 lines of glue tied to internal APIs.Response. Test the handler payload directly.If middleware behavior or Response shape ever needs to be tested, revisit the integration harness option from the opening post.
All 17 server fns under src/features/**/api/*-server.ts (plus src/features/auth/server/auth-server.ts) have matching *.test.ts files using this pattern. Reference implementations:
src/features/users/api/users-server.test.tssrc/features/users/api/create-user-server.test.ts