tanstack-start-server-fn-testing

Unit-test TanStack Start createServerFn handlers via a global vi.mock that combines two patterns from Discussion #2701

9 stars

Best use case

tanstack-start-server-fn-testing is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Unit-test TanStack Start createServerFn handlers via a global vi.mock that combines two patterns from Discussion #2701

Teams using tanstack-start-server-fn-testing should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/tanstack-start-server-fn-testing/SKILL.md --create-dirs "https://raw.githubusercontent.com/sc30gsw/claude-code-customes/main/sample/harness/tanstack-start/skills/tanstack-start-server-fn-testing/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/tanstack-start-server-fn-testing/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How tanstack-start-server-fn-testing Compares

Feature / Agenttanstack-start-server-fn-testingStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Unit-test TanStack Start createServerFn handlers via a global vi.mock that combines two patterns from Discussion #2701

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# TanStack Start Server Function Unit Testing

**Updated:** 2026-05-12
**Context:** Testing internal logic of `createServerFn`-defined server functions with Vitest, without spinning up the full TanStack Start server runtime.

## Problem

`createServerFn` is a server-only API. Importing it normally in tests fails because:

1. The `'use server'` pragma check throws `Invariant failed: createServerFn must be called with a function that is marked with the 'use server' pragma` at runtime.
2. The `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.

## Solution: combine two patterns from Discussion #2701

This project's solution lives in `src/test/server-fn-mock.ts` and is built from **two posts in [TanStack Router Discussion #2701](https://github.com/TanStack/router/discussions/2701)** combined into one approach.

### Source 1 — overall direction (the opening post)

- URL: <https://github.com/TanStack/router/discussions/2701#discussion-7425606>
- Author: `cameronb23` (Nov 2024)
- Take: "the best that I have come up with is mocking the call globally."
- Code snippet (old `createServerFn(method, fn)` signature):
  ```ts
  vi.mock(import("@tanstack/start"), async (importOriginal) => {
    const original = await importOriginal();
    return {
      ...original,
      createServerFn: (_ignoredMethod, fn) => fn,
    };
  });
  ```
- We adopt the **global-mock direction** from this post. We do **not** adopt the literal code because it targets a legacy `createServerFn` signature; the current API is a builder.

### Source 2 — builder-aware mock (Enhanced)

- URL: <https://github.com/TanStack/router/discussions/2701#discussioncomment-15184454>
- Take: hoist a builder object that satisfies the full `.middleware().inputValidator().handler()` chain.
- Code snippet:
  ```ts
  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),
  }));
  ```
- We adopt the **builder structure** from this comment, because our server fns use `.inputValidator(...).handler(...)`.

### Our project-specific divergence

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:

```ts
inputValidator(validate) {
  return {
    handler(fn) {
      return (opts) => fn({ data: validate(opts.data) })
    },
  }
}
```

## Required setup

Three pieces work together:

### 1. `src/test/server-fn-mock.ts`

Holds the `vi.mock("@tanstack/react-start", ...)` factory described above. The file's top-level comment cites both Discussion #2701 URLs.

### 2. `src/test/setup.ts`

Imports the mock as a side effect so it applies to every test:

```ts
import "~/test/server-fn-mock";
```

### 3. `vite.config.ts` — disable `tanstackStart()` plugin during Vitest

```ts
const 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`.

## Writing tests

After the setup above, server fn tests are plain imports + calls — no `import?raw`, no `new Function`, no regex source rewriting.

### GET (no input validation)

```ts
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);
  });
});
```

### POST with `inputValidator` — happy path + validation error

```ts
import * 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);
  });
});
```

## What this approach does NOT cover

- Global / route middleware execution (Discussion #2701 also proposes an "integration harness" using `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 / Headers / Status assertions: the mock returns the handler's raw value, not a `Response`. Test the handler payload directly.
- Nested server-fn calls (one server fn calling another): not supported by either approach in Discussion #2701.

If middleware behavior or `Response` shape ever needs to be tested, revisit the integration harness option from the opening post.

## Examples in this project

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:

- GET: `src/features/users/api/users-server.test.ts`
- POST + inputValidator: `src/features/users/api/create-user-server.test.ts`

## References

- Discussion thread: <https://github.com/TanStack/router/discussions/2701>
- Pattern 1 (global mock direction): <https://github.com/TanStack/router/discussions/2701#discussion-7425606>
- Pattern 2 (builder structure): <https://github.com/TanStack/router/discussions/2701#discussioncomment-15184454>

Related Skills

webapp-testing

9
from sc30gsw/claude-code-customes

Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.

tanstack-start

9
from sc30gsw/claude-code-customes

Full-stack React framework powered by TanStack Router with SSR, streaming, server functions, and deployment to any hosting provider.

test

9
from sc30gsw/claude-code-customes

Advanced test implementation command with unit/E2E support, auto-execution, and smart fixing capabilities

serena

9
from sc30gsw/claude-code-customes

Token-efficient Serena MCP command for structured app development and problem-solving

project-guidelines-example

9
from sc30gsw/claude-code-customes

Example project-specific skill template based on a real production application.

notion-bug-pr

9
from sc30gsw/claude-code-customes

Skill that pulls bug tickets (titles containing 「不具合」) from a Notion database, investigates root cause in a GitHub repo, applies fixes, and opens draft PRs. Supports three modes — daily recurring schedule, one-shot at a specific time, or immediate on-demand run. Takes three or four args: Notion database URL, repo path or name, and either HH:MM (daily), "once HH:MM" (one-shot), or "now" (immediate).

graphify

9
from sc30gsw/claude-code-customes

any input (code, docs, papers, images, videos) to knowledge graph. Use when user asks any question about a codebase, documents, or project content - especially if graphify-out/ exists, treat the question as a /graphify query.

chrome

9
from sc30gsw/claude-code-customes

Comprehensive Chrome DevTools development system with native Chrome capabilities for debugging, E2E testing, performance analysis, and browser automation

web-design-guidelines

9
from sc30gsw/claude-code-customes

Review UI code for Web Interface Guidelines compliance. Use when asked to "review my UI", "check accessibility", "audit design", "review UX", or "check my site against best practices".

typescript-advanced-types

9
from sc30gsw/claude-code-customes

Master TypeScript's advanced type system including generics, conditional types, mapped types, template literals, and utility types for building type-safe applications. Use when implementing complex type logic, creating reusable type utilities, or ensuring compile-time type safety in TypeScript projects.

tailwind-css-patterns

9
from sc30gsw/claude-code-customes

Provides comprehensive Tailwind CSS utility-first styling patterns including responsive design, layout utilities, flexbox, grid, spacing, typography, colors, and modern CSS best practices. Use when styling React/Vue/Svelte components, building responsive layouts, implementing design systems, or optimizing CSS workflow.

skill-creator

9
from sc30gsw/claude-code-customes

Create new skills, modify and improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, edit, or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy.