sample/harness/tanstack-start/skills/tanstack-start-server-fn-testing/SKILL.md
Unit-test TanStack Start createServerFn handlers via a global vi.mock that combines two patterns from Discussion #2701
npx skillsauth add sc30gsw/claude-code-customes tanstack-start-server-fn-testingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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: "[email protected]", name: "Alice", role: "admin" } }),
).resolves.toMatchObject({ email: "[email protected]" });
});
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.tstools
Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations.
testing
# sdd-workflow — Workflow Status Dashboard ## Slash Command ``` /sdd-workflow [slug] ``` ## Purpose Read-only meta skill. Displays the current state of the SDD workflow — which phases are complete, which is next, and any blockers. Does NOT modify any files. --- ## This Skill is Read-Only `sdd-workflow` never writes to or modifies any file. It only reads spec files and git history to report status. There is no approval gate for this skill. --- ## Usage: Specific Feature ``` /sdd-workflo
content-media
# sdd-tasks **Slash command**: `/sdd-tasks <slug>` **Purpose**: Generate `tasks.md` (TASK-001..N) and `progress.md` from `requirements.md` and `design.md`. --- ## Prerequisites - `.claude/specs/<slug>/requirements.md` must exist - `.claude/specs/<slug>/design.md` must exist (run `/sdd-design` first) --- ## Steps ### 1. Read spec inputs ``` .claude/specs/<slug>/requirements.md .claude/specs/<slug>/design.md ``` Extract: - Every REQ-XXX ID with its acceptance criteria - Every design sect
development
# sdd-review — Post-Implementation Code Review ## Slash Command ``` /sdd-review <slug> ``` ## Purpose Run code review and security review on all changes introduced by the feature branch. Append structured findings to `review.md`. Does NOT auto-apply fixes — only proposes them. --- ## Prerequisites - `sdd-impl` has completed: all tasks in `progress.md` are `done` (or at least one is `done`; partial reviews are allowed). - The feature branch must have at least one commit ahead of `main`. -