# Nfs Testing Patterns

> Test-writing patterns for projects scaffolded with nextjs-fullstack-starter. Use whenever the user is writing or reviewing tests in such a project, asks 'how do I test X', wants to add test coverage for a new module, or needs to debug a failing test. Covers service-layer unit tests (the high-value layer where most coverage lives), integration tests with a real Postgres (Testcontainers), Server Action tests via direct invocation, route-handler tests, and Playwright e2e. Each section explains WHAT to test at that layer and HOW so test effort lands where it pays off.

- Skill: `juncoding/nfs-testing-patterns` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add juncoding/nfs-testing-patterns`
- Raw SKILL.md: https://api.skillmd.com/api/skills/juncoding/nfs-testing-patterns/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: juncoding (https://skillmd.com/u/juncoding)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/juncoding/nfs-testing-patterns

---


# Testing patterns for the fullstack stack

For projects scaffolded with `nextjs-fullstack-starter`. Tests are first-class — the verification gate (`pnpm verify`) runs them on every PR.

## Use this skill when

- Writing tests for a new service, action, or route handler.
- Adding coverage to existing code.
- Debugging a failing test.
- Deciding what to test at which layer.
- Reviewing a PR's test coverage.

## The shape of testing in this stack

The architecture has four delivery shapes — Server Components, Server Actions, route handlers, MCP tools — all calling services. **Most of your testing effort goes into the service layer.** Delivery wrappers are thin enough that integration tests cover them implicitly.

Roughly:

| Layer | Test type | Why |
|---|---|---|
| **Service** | Unit tests with mocked Prisma | Where the business logic lives. Highest leverage. |
| **Service** (occasional) | Integration tests against real Postgres | When Prisma's query builder behaviors matter — joins, transactions, indexes. |
| **Server Action** | Direct invocation tests OR e2e | Thin wrapper — usually covered by e2e. Unit-test only when the action does non-trivial parsing. |
| **Route handler** | Direct invocation tests | Same as actions — thin. Test webhook signature verification and edge cases. |
| **Page / UI** | Playwright e2e | Server Components are server-rendered HTML; integration testing them is e2e. |
| **MCP tool** | Same as routes/actions — thin wrapper test | Verifies the tool registry contract; service logic is covered by service tests. |

## Reference index

Read the file matching what you're testing:

| Testing this | Read this |
|---|---|
| A service method | `references/service-tests.md` |
| A Server Action | `references/server-action-tests.md` |
| A route handler / webhook | `references/route-handler-tests.md` |
| A page or user flow end-to-end | `references/e2e-playwright.md` |

## Quick rules

1. **Mock at the boundary, not the layer above.** When unit-testing a service, mock `@/server/db/client` and `@/server/auth/permissions`. Don't mock other services it calls — let those use their real (also-mocked-Prisma) implementations.
2. **Test inputs and outputs, not internals.** Don't assert on what private helpers got called. Assert on what came back and what the DB was asked to do.
3. **Use real `db.$transaction` semantics.** When mocking Prisma, the `$transaction` mock should pass a `tx` shaped like the same client. Otherwise audit-inside-transaction patterns appear broken in tests.
4. **Permission denials get their own tests.** `requirePermission` is critical — every service should have at least one test that confirms the denial path throws.
5. **Don't test types.** TypeScript runs ahead of every test; type-only invariants are already checked.
6. **Don't test the framework.** No test for `requireSession` redirecting — that's Better Auth's job. Trust the upstream.

## Running tests

```bash
pnpm test           # one-shot
pnpm test:watch     # watch mode
pnpm test -- customer       # filter by name
pnpm test -- --updateSnapshot
```

## The verification gate

```bash
pnpm verify
```

Chains `format:check`, `tsc --noEmit`, `lint`, `build`, `test`, `db:diff`. CI runs the same — the gate is canonical. If tests are flaky, that's a test bug, not a CI bug. Fix it; don't retry.

