Quality
Testing
Generated projects come with static checks but no test runner, so you choose the tools that fit your team. This page covers what runs today, how to add tests, and where tests belong in an FSD project.
What runs out of the box
npm run ci runs the FSD architecture check (Steiger), lint, type checks where the template defines them, and a production build. It catches broken imports, layer violations, and type errors before any test is written.
npm run ci # fsd:check, lint, typecheck, build
npm run fsd:check # architecture rules onlyBefore generating, preview the exact files and route edits with --dry-run. Generators also roll back automatically when a step fails, so a half-written slice never reaches your tests.
Add unit and component tests with Vitest
Vitest reuses the project's Vite config, including the @/ alias, so FSD imports work in tests unchanged. Install the packages for your framework:
| Framework | npm install -D |
|---|---|
| React + Vite | vitest jsdom @testing-library/react @testing-library/jest-dom |
| Vue + Vite | vitest jsdom @vue/test-utils |
| SvelteKit | vitest jsdom @testing-library/svelte |
| Next.js | vitest jsdom @vitejs/plugin-react @testing-library/react |
| Nuxt | vitest @nuxt/test-utils @vue/test-utils happy-dom |
For the Vite-based templates, extend the existing config:
// vitest.config.ts
import { defineConfig, mergeConfig } from "vitest/config";
import viteConfig from "./vite.config";
export default mergeConfig(
viteConfig,
defineConfig({ test: { environment: "jsdom" } }),
);Next.js has no Vite config, so declare the React plugin and the @ alias in vitest.config.ts. Nuxt uses defineVitestConfig from @nuxt/test-utils/config. Then add the script and include it in the gate:
"scripts": {
"test": "vitest run",
"ci": "npm run fsd:check && npm run lint && npm run test && npm run build"
}Where tests live
Keep a test next to the code it covers, inside the same slice and segment. Deleting a slice then deletes its tests too, and the import rule applies to tests like any other file.
src/entities/product/
├── index.ts
├── model/product.types.ts
├── model/price.ts
├── model/price.test.ts # unit test for slice logic
├── ui/product-card.tsx
└── ui/product-card.test.tsx # component testTests from other layers import through the public API, the same way production code does:
// src/entities/product/ui/product-card.test.tsx
import { render, screen } from "@testing-library/react";
import { expect, test } from "vitest";
import { ProductCard } from "@/entities/product";
test("shows the product name", () => {
render(<ProductCard product={{ id: "1", name: "Desk lamp" }} />);
expect(screen.getByRole("heading", { name: "Desk lamp" })).toBeDefined();
});End-to-end tests
Page-level flows cross every layer, so test them in a real browser. Playwright works with every template:
npm init playwright@latest
npx playwright testKeep end-to-end specs outside src, for example in e2e/, so Steiger and the FSD layers stay about application code.