Skip to content

Applies to CLI v2.7.1Last updated

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.

bash
npm run ci          # fsd:check, lint, typecheck, build
npm run fsd:check   # architecture rules only

Before 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:

Frameworknpm install -D
React + Vitevitest jsdom @testing-library/react @testing-library/jest-dom
Vue + Vitevitest jsdom @vue/test-utils
SvelteKitvitest jsdom @testing-library/svelte
Next.jsvitest jsdom @vitejs/plugin-react @testing-library/react
Nuxtvitest @nuxt/test-utils @vue/test-utils happy-dom

For the Vite-based templates, extend the existing config:

typescript
// 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:

json
"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.

text
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 test

Tests from other layers import through the public API, the same way production code does:

tsx
// 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:

bash
npm init playwright@latest
npx playwright test

Keep end-to-end specs outside src, for example in e2e/, so Steiger and the FSD layers stay about application code.

Support FSD CLI

If FSD CLI helps you build, consider supporting its development and documentation. Donations are optional.

GitHub Sponsors (opens in a new tab)Buy Me a Coffee (opens in a new tab)

InstaPay · Egypt

ashrafmo-1

للدعم من مصر عبر إنستا باي. تأكد من بيانات المستلم قبل تأكيد التحويل.