.agents/skills/unit-testing/SKILL.md
Use when writing or modifying unit tests in this project. Load this skill before creating any *.spec.ts file. Covers all five Vitest projects, mock patterns, faketories, composable/store/repository test wiring, and coverage requirements.
npx skillsauth add antoinezanardi/goat-it-web-admin unit-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.
Full human-readable reference: docs/unit-testing.md
Read it for complete examples. This skill contains the non-negotiable rules and decision trees.
docs/unit-testing.md in full.pnpm run test:unit file.spec.tspnpm run test:unit:cov| Source file path | Spec suffix | Project |
|--------------------------------------------------------------------------------|-------------------------------------------|----------------|
| app/composables/**/*.ts | .spec.ts | composables |
| app/**/*.store.ts | .store.spec.ts | stores |
| app/**/*.repository.ts | .repository.spec.ts | repositories |
| app/**/*.{mappers,helpers,translations}.ts | .{mappers,helpers,translations}.spec.ts | node |
| server/**/*.{mappers,helpers}.ts | .{mappers,helpers}.spec.ts | node |
| shared/**/*.{mappers,helpers}.ts | .{mappers,helpers}.spec.ts | node |
| Everything else in app/, server, shared/ that does not match a row above | .spec.ts | nuxt |
The repositories and node projects have no Nuxt environment. No mountSuspended, no mockNuxtImport, no global $fetch.
"<ComponentName> Component" — never a direct reference: describe("MyComponent Component", ...).describe(myFn, ...).describe("Server Goat It API Items Get Handler", ...))."should <action> when <condition>." — always end with a period.toHaveBeenCalledExactlyOnceWith(...) — never combine toHaveBeenCalledTimes(1) + toHaveBeenCalledWith(...).$t returns the key as-is. Assert the translation key string, never the translated text.vi.resetModules() runs before every test (global setup).await import(...) inside beforeEach — never at the top level.nuxt project)#componentsdescribe("MyComponent Component", ...) — string label, not a component referenceconst at the top of describe, before the mount helperasync function mountXxxComponent(options: MountSuspendedOptions<typeof Xxx> = {})beforeEach: mount with defaults, then mockStore(useXxxStore) after mountSuspendedshallow: true: in the template). Skip static string props without : (e.g. variant="subtle", color="neutral")nuxt project)import MyPage from "@/pages/my-page.vue"shallow: true in mount helperdefinePageMeta was called with expected metadatanuxt project)spec/ subfolder: app/layouts/MyLayout/spec/MyLayout.spec.ts#components)shallow: truecomposables project)mockNuxtImport(...) at module level for each dependencyimport type { useFoo as UseFooType }let useFoo: typeof UseFooType at module levelbeforeEach: recreate mocks, then ({ useFoo } = await import(...))stores project)mockNuxtImport("useAsyncAction", ...) to capture capturedAction and capturedOnErrorundefined at the top of each beforeEachbeforeEachcapturedAction is toBe(repository($fetch).method) (strict reference equality)capturedOnError?.() triggers useAppToast().addErrorToast with the correct i18n keyrepositories project)fetchMock = vi.fn<$Fetch>() in beforeEachfetchMock as $Fetch to the factorytoStrictEqual<ExpectedType>(value) for return assertionsnuxt project)vi.mock(import("#server/utils/goat-it-api/helpers/goat-it-api.helpers")) at module levelvi.mocked($fetch).mockResolvedValue(...) in beforeEach{} as unknown as H3Event for events (add params as needed)createGoatItApiEndpoint callcreateGoatItApiFetchOptions call with expected runtime config (baseUrl: "https://api.goat-it.com", adminKey: "test-admin-key")$fetch call with correct endpoint + optionsZodError is thrown for invalid API datanode project)#server/utils/...node project)~~/shared/utils/...node project)app/i18n/specs/ (NOT colocated)crush from radashi to flatten keystoSorted() EN keys equal toSorted() FR keys| Utility | Path | Purpose |
|----------------------------------------|---------------------------------------------------------------------------------------------------------------|-----------------------------------------------|
| ToMock<T> | ~~/tests/unit/utils/types/mock.types | Types a mock object matching interface T |
| MockedPiniaStore<T> | ~~/tests/unit/utils/types/mock.types | Types a mocked Pinia store |
| mockStore(useStore) | ~~/tests/unit/utils/mocks/stores/store.mock | Returns useStore() as MockedPiniaStore<T> |
| MountSuspendedOptions<C> | ~~/tests/unit/utils/types/mount.types | Type for the options arg of mountSuspended |
| createUseFetchStatusMock() | ~~/tests/unit/utils/mocks/composables/core/useFetchStatus/useFetchStatus.mock | Mock factory |
| createUseAsyncActionMock() | ~~/tests/unit/utils/mocks/composables/core/useAsyncAction/useAsyncAction.mock | Mock factory |
| createUseAppToastMock() | ~~/tests/unit/utils/mocks/composables/ui/useAppToast/useAppToast.mock | Mock factory |
| createQuestionThemesRepositoryMock() | ~~/tests/unit/utils/mocks/repositories/goat-it-api/questionThemesRepository/question-themes-repository.mock | Mock factory |
tests/unit/utils/mocks/composables/<category>/<ComposableName>/useXxx.mock.tstests/unit/setup/nuxt/composables/use-xxx.nuxt.unit-setup.ts — use mockNuxtImportVITEST_COMPOSABLES_MOCK_SETUP_FILES in configs/vitest/vitest.config.constants.tstests/unit/utils/mocks/repositories/goat-it-api/<Resource>/xxx-repository.mock.tstests/unit/setup/nuxt/repositories/xxx-repository.nuxt.unit-setup.ts — use vi.mock(...) (NOT mockNuxtImport)VITEST_REPOSITORIES_MOCK_SETUP_FILES in configs/vitest/vitest.config.constants.ts// Entity faketory pattern
function createFakeMyEntity(myEntity: Partial<MyEntity> = {}): MyEntity {
return {
id: faker.database.mongodbObjectId(),
slug: faker.lorem.slug(),
status: faker.helpers.arrayElement(MY_ENTITY_STATUSES),
createdAt: faker.date.anytime(),
updatedAt: faker.date.anytime(), ...myEntity, // must be last
};
}
tests/unit/utils/faketories/<entity-name>/entity/ and …/dto/Partial<T> = {} and spread it lastDate objectsIn nuxt, composables, and stores projects, the following are available without any setup in your test file:
$t(key) → returns key unchanged (global Vue mock)$tc(key, count) → returns key unchanged (global Vue mock)definePageMeta → Vitest spy (accessible globally)useI18n() → mock returning { t: (key) => key, locale: ref("en") }useRouter() → mock$fetch → vi.fn() spy (reset each test)useToast() → mockgetRouterParam → global stubreadBody → global stubcreateError → mock via vi.hoisteduseHead → mockcallOnce → mockquestionThemesRepository → vi.mock(...) mock (nuxt + composables + stores projects)2026-04-14 UTCtests/unit/setup/nuxt/composables/ to get the current authoritative list; each file there registers one global mock.In the stores project additionally:
setActivePinia(createPinia()) runs before each testBecause these mocks are registered globally, you do not need mockNuxtImport in your spec file.
Call the composable directly in the test body — you get back the same mock instance the component received.
Mutate it, then await nextTick() to let the template react.
import { nextTick } from "vue";
it("should show dark tooltip when color mode is light.", async () => {
const colorMode = useColorMode();
colorMode.value = "light";
await nextTick();
expect(wrapper.find("#my-tooltip").attributes("text")).toBe("navigation.switchOnDarkMode");
});
This pattern applies to any composable listed in tests/unit/setup/nuxt/composables/ (e.g. useColorMode, useAsyncAction, useAppToast, useFetchStatus, …).
Pitfall: Do NOT add
mockNuxtImport("useFoo", ...)in a component spec whenuseFoois already globally mocked. Just calluseFoo()directly in the test body.
development
Use when working with VueUse composables - track mouse position with useMouse, manage localStorage with useStorage, detect network status with useNetwork, debounce values with refDebounced, and access browser APIs reactively. Check VueUse before writing custom composables - most patterns already implemented.
tools
Use when working with Nuxt 4 concepts — routing, composables, data fetching, server routes, layouts, middleware, plugins, auto-imports, SSR/hydration, runtime config, state management, error handling, and testing. Load this skill before writing or modifying any Nuxt-specific code in this project.
development
Build UIs with @nuxt/ui v4 — 125+ accessible Vue components with Tailwind CSS theming. Use when creating interfaces, customizing themes to match a brand, building forms, or composing layouts like dashboards, docs sites, and chat interfaces.
testing
Create, edit, improve, or audit AgentSkills. Use when creating a new skill from scratch or when asked to improve, review, audit, tidy up, or clean up an existing skill or SKILL.md file. Also use when editing or restructuring a skill directory (moving files to references/ or scripts/, removing stale content, validating against the AgentSkills spec). Triggers on phrases like "create a skill", "author a skill", "tidy up a skill", "improve this skill", "review the skill", "clean up the skill", "audit the skill".