Testing Overview
Testing changes should start with one question: which layer gives the cheapest useful signal for the behavior being changed?
Use this page for layer-selection guidance, command surfaces, and a map of the current test surfaces. Use Testing Authoring Patterns for mocks, recipes, naming, and LLM prompts. Use Testing Runtime And CI for browser runtime contracts, CI jobs, i18n verification, and smoke posture.
Layer Selection
Section titled “Layer Selection”Test each concern at its lowest meaningful layer exactly once. Duplicate coverage only when the second layer protects a different failure mode.
| Layer | Use In Context Of | Avoid Using It For |
|---|---|---|
| Unit tests | Leaf components, pure helpers, utilities, narrow branching behavior. | Proving app provider wiring, route behavior, persistence, or browser UX. |
| Front-end integration | Components plus providers, route state, auth state, query clients, hydration, DOM effects. | Exhaustive browser coverage or backend persistence behavior. |
| Client network/API tests | Axios envelope handling, retry behavior, token refresh, redirects, request adapters. | Re-testing backend handler internals. |
| Backend request tests | Hono route wiring, middleware composition, request validation, envelope shape, branching. | Real database mutation outcomes or provider-specific object-store behavior. |
| DB-backed integration | Persistence behavior, query shape, relational correctness, transaction rollback, auth DB chains. | Schema validation branches that fail before the DB is touched. |
| Adapter tests | External provider behavior, path construction, upload/delete semantics, URL resolution. | Ordinary app-logic paths where a storage double gives the same signal. |
| Browser E2E | Real browser flows through implemented surfaces and high-value user journeys. | Copy-only permutations, placeholder pages, or broad regression coverage. |
| Deployed smoke | The selected deployed target, cloud state, runtime config, artifacts, and public URL. | Local correctness or pre-deploy repo health. |
Choosing The Shape
Section titled “Choosing The Shape”Before writing a test, name the failure mode in one sentence. Then choose the layer that observes that failure mode with the least machinery.
Good test descriptions usually follow this shape:
- Subject: The component, route, helper, adapter, or journey under test.
- Condition: The meaningful input, role, env mode, route state, persistence state, or provider response.
- Expected behavior: The user-visible result, contract parse, persisted row, adapter call, redirect, or emitted error.
For example:
Hides the registration code field in standard mode and omits it from the submission payload.Returns a conflict error when the provided email is already taken.Uploads artist media and persists canonical locator fields from mocked storage.Redirects an unauthenticated user to login and returns them to the protected route after login.
If that sentence mentions a DOM prop or pure transform, start with a unit test. If it mentions providers, route state, query cache, or forms submitting through component composition, use a front-end integration test. If it mentions Hono middleware, route mounting, request validation, or envelope shape, use a mocked backend request test. If it mentions a row, relationship, transaction, or seeded auth chain, use a DB-backed test. If it mentions browser navigation, cookies, focus, portals, media rendering, or a real app journey, use Playwright.
Arrange tests so the setup teaches the boundary:
Arrange: fixtures, env, route state, query cache, module mocks, or seeded rows.Act: one user action, request, helper call, or browser journey step.Assert: one primary behavior, plus the smallest side-effect proof that makes the behavior durable.Command Surfaces
Section titled “Command Surfaces”Prefer the narrowest command that proves the boundary you changed.
Root commands:
pnpm testpnpm test:packagespnpm test:unitpnpm test:watchpnpm test:coveragepnpm test:cipnpm test:e2e:preparepnpm test:e2e:verify-source-runtimepnpm format:i18npnpm verify:i18npnpm verify:scriptspnpm verify:github-actionspnpm build:docspnpm smoke:docsCurrent behavior:
pnpm testandpnpm test:cirun the shared-package Vitest suites, then the front-end Vitest suite.pnpm test:packagesruns@wavemap/shared-utils,@wavemap/api-contracts, and@wavemap/i18n.pnpm test:unitruns only the front-end Vitest suite.- Browser E2E is intentionally outside the root aggregate test command.
pnpm test:e2e:preparebuilds workspace package outputs that front-end Playwright source imports need.pnpm test:e2e:verify-source-runtimeprepares those package outputs, then verifies Playwright source-runtime import and discovery behavior.pnpm -C apps/wavemap-front-end test:e2e:smokeruns the explicit smoke suite manifest, not every browser spec.pnpm -C apps/wavemap-front-end test:e2eruns all currently discovered front-end browser specs for local iteration.
Front-end commands:
pnpm -C apps/wavemap-front-end testpnpm -C apps/wavemap-front-end test:watchpnpm -C apps/wavemap-front-end test:coveragepnpm -C apps/wavemap-front-end test:cipnpm -C apps/wavemap-front-end test:e2epnpm -C apps/wavemap-front-end test:e2e:smokepnpm -C apps/wavemap-front-end test:e2e:deployed-browser-routingpnpm -C apps/wavemap-front-end test:e2e:deployed-cold-startpnpm -C apps/wavemap-front-end test:e2e:deployed-media-deliverypnpm -C apps/wavemap-front-end test:e2e:headedpnpm -C apps/wavemap-front-end test:e2e:uipnpm -C apps/wavemap-front-end exec playwright install chromiumBack-end commands:
pnpm -C apps/wavemap-back-end testpnpm -C apps/wavemap-back-end test:watchpnpm -C apps/wavemap-back-end test:integrationpnpm -C apps/wavemap-back-end test:cipnpm -C apps/wavemap-back-end test:db-backedpnpm -C apps/wavemap-back-end test:db-backed:s3-emulatorpnpm -C apps/wavemap-back-end test:db-backed:watchShared-package commands:
pnpm -C packages/shared-utils testpnpm -C packages/api-contracts testpnpm -C packages/i18n testDocs commands:
pnpm build:docspnpm smoke:docsCurrent Test Surfaces
Section titled “Current Test Surfaces”Front End
Section titled “Front End”The front-end Vitest suite is the broadest local test surface.
It covers:
- Unit tests for reusable components, helpers, utilities, and API/client boundaries.
- Integration tests for provider composition, route state, auth state, query clients, hydration, suspense, forms, and DOM
behavior in
jsdom. - Client network tests for axios envelopes, retries, redirects, token refresh behavior, and API adapters.
Front-end tests live mostly under apps/wavemap-front-end/src/**/__tests__, with shared setup and helpers under
apps/wavemap-front-end/test/unit-and-integration.
Front-End Unit And Component Tests
Section titled “Front-End Unit And Component Tests”Use direct render(...) for leaf UI when the behavior is local to the component. A reusable component test should
usually cover:
- The developer-facing prop surface.
- The meaningful states a caller can select.
- One or two basic interactions.
- Class, style, ARIA, or DOM output that is part of the component contract.
Example shape:
render(<Button disabled />)
const button = screen.getByTestId("button")
expect(button).toHaveAttribute("data-disabled", "true")Good front-end unit and component entry points:
apps/wavemap-front-end/src/components/ButtonNew/__tests__/ButtonNew.test.tsxapps/wavemap-front-end/src/components/FileUploadSurface/__tests__/FileUploadSurface.test.tsxapps/wavemap-front-end/src/components/Carousel/__tests__/Carousel.test.tsxapps/wavemap-front-end/src/components/TimePicker/__tests__/TimePicker.test.tsxapps/wavemap-front-end/src/utils/media/__tests__/mediaDraft.test.tsFront-End Integration Tests
Section titled “Front-End Integration Tests”Use this type of test when the behavior needs app composition, route state, current user state, query-cache state, i18n, locale
providers, hydration, suspense, or form submission through a realistic wrapper. These tests still run in jsdom, so they
should focus on application state and DOM behavior rather than true browser layout or navigation timing.
Use renderWithAppProviders(...) when the behavior needs any of the real app wrapper surface: locale context, i18n,
React Query, route state, app shell, overlay provider, hydration, or current user data. The helper accepts explicit test
inputs for those concerns so they stay visible in the test:
const { container } = renderWithAppProviders(<RegistrationForm mode={REGISTRATION_FORM_MODE__STANDARD} />, { locale: LOCALE__EN, preloadNamespaces: ["page--registration", "common--form-validation"], pathname: "/en/registration", initialUser: null,})When text is translated, prefer setting the small translation surface the test needs instead of depending on full locale resources:
beforeEach(() => { setI18nMockNamespaceTranslations("page--registration", { "form.email.placeholder": "Email", "actions.sign-up": "Sign Up", })})That keeps component tests stable while still showing which namespace and keys the component expects. If the copy is not the behavior under test, assert by role, label, test ID, or submitted payload instead of repeating large translation fixtures.
Good front-end integration entry points:
apps/wavemap-front-end/src/components/Forms/RegistrationForm/__tests__/RegistrationForm.integration.test.tsxapps/wavemap-front-end/src/app/[locale]/(auth)/login/InternalComponents/Login/__tests__/Login.integration.test.tsxapps/wavemap-front-end/src/utils/auth/__tests__/useRequireAuth.integration.test.tsxapps/wavemap-front-end/src/app/[locale]/(artists)/artist/[artistID]/InternalComponents/ArtistDetails/__tests__/ArtistDetails.integration.test.tsxapps/wavemap-front-end/src/components/Forms/AddOrEditArtistForm/__tests__/AddOrEditArtistForm.integration.test.tsxClient API And Network Tests
Section titled “Client API And Network Tests”Use this type for client-side request adapters, Axios envelope handling, token refresh, redirects, React Query hooks, and mutation/query cache behavior. Mock the HTTP boundary or API module being exercised, but keep the public hook or adapter surface real so callers learn the same contract production code uses.
Good client API and network entry points:
apps/wavemap-front-end/src/api/axios/__tests__/api.test.tsapps/wavemap-front-end/src/api/axios/__tests__/interceptors.test.tsapps/wavemap-front-end/src/api/axios/__tests__/auth/auth.test.tsapps/wavemap-front-end/src/api/mutations/__tests__/auth/useLogin.integration.test.tsxapps/wavemap-front-end/src/api/queries/__tests__/artists/useGetArtists.integration.test.tsxBack End
Section titled “Back End”The backend has two package-local Vitest suites.
Mocked Backend Request Tests
Section titled “Mocked Backend Request Tests”The mocked request suite lives under apps/wavemap-back-end/test/mocked-requests. It exercises the Hono app boundary
through app.request(...) with mocked database, query, email, and storage seams as needed. Use it for route wiring,
middleware composition, request validation, response envelope shape, and handler branching behavior.
Good mocked backend request entry points:
apps/wavemap-back-end/test/mocked-requests/__tests__/auth/registration.route.test.tsapps/wavemap-back-end/test/mocked-requests/__tests__/auth/login.route.test.tsapps/wavemap-back-end/test/mocked-requests/__tests__/artists/createArtist.route.test.tsapps/wavemap-back-end/test/mocked-requests/__tests__/users/pageQueryPresetsImportOnboarding.route.test.tsapps/wavemap-back-end/test/mocked-requests/__tests__/contracts/routeMounts.test.tsDB-Backed Backend Integration Tests
Section titled “DB-Backed Backend Integration Tests”The DB-backed suite lives under apps/wavemap-back-end/test/db-backed. It runs against the Docker-backed development
Postgres database through vitest.db-backed.config.ts, with files run sequentially against the shared database. Use it
when the behavior being protected is real persistence, query shape, transaction behavior, relationship mutation, or the
auth chain through real seeded user data.
The mocked request suite and DB-backed suite often test the same route family, but they should not test the same confidence. A mocked request test is good for “does this route reject a bad body and return the right error envelope?” A DB-backed test is good for “does this successful upload produce the expected media rows and storage locators?”
Good DB-backed backend entry points:
apps/wavemap-back-end/test/db-backed/__tests__/artists/artistMedia.route.test.tsapps/wavemap-back-end/test/db-backed/__tests__/artists/artistMedia.s3-emulator.route.test.tsapps/wavemap-back-end/test/db-backed/__tests__/auth/registration.route.test.tsapps/wavemap-back-end/test/db-backed/__tests__/auth/login.route.test.tsapps/wavemap-back-end/test/db-backed/__tests__/pageQueryPresets/pageQueryPresets.route.test.tsBackend Handler And Utility Unit Tests
Section titled “Backend Handler And Utility Unit Tests”Handler-level tests under apps/wavemap-back-end/src/**/__tests__ are useful when a route outcome depends on a branch
that is awkward or expensive to create through a full request. They can mock middleware, database calls, or storage
adapters more aggressively, but should still exercise the real handler code and a realistic request shape.
Use utility unit tests when the subject is pure or mostly pure backend logic: runtime configuration resolution, authorization helpers, collision retry policy, media reconciliation, JWT behavior, or error mapping. These tests should stay small and should not reach for Hono or a database unless the behavior is really at that boundary.
Good backend handler and utility entry points:
apps/wavemap-back-end/src/handlers/artists/__tests__/uploadArtistMedia.test.tsapps/wavemap-back-end/src/handlers/artists/__tests__/deleteArtistMedia.test.tsapps/wavemap-back-end/src/handlers/artists/__tests__/syncArtistMedia.test.tsapps/wavemap-back-end/src/handlers/artists/__tests__/updateArtist.test.tsapps/wavemap-back-end/src/utils/__tests__/mediaStorageRuntimeConfig.test.tsAdapter Tests
Section titled “Adapter Tests”Adapter tests protect translation boundaries between Wavemap domain code and another shape: object storage providers, database rows converted to API DTOs, uploaded file metadata, or provider-specific URL construction. Use them when the contract is mostly about mapping, path construction, or provider behavior rather than route composition.
Good adapter entry points:
apps/wavemap-back-end/src/utils/__tests__/s3MediaStorageAdapter.test.tsapps/wavemap-back-end/src/utils/__tests__/mockedMediaStorageAdapter.test.tsapps/wavemap-back-end/src/utils/__tests__/mediaStorage.test.tsapps/wavemap-back-end/src/adapters/DBtoAPI/artists/__tests__/artistMedia.test.tsapps/wavemap-back-end/src/adapters/DBtoAPI/artists/__tests__/artistDetails.test.tsShared Package Tests
Section titled “Shared Package Tests”All shared packages own their own Vitest config and test command. Keep package tests local unless a behavior genuinely spans packages.
Current package test locations include:
packages/shared-utils/src/utils/__tests__packages/api-contracts/src/utils/__tests__packages/i18n/src/core/utils/__tests__
Shared-package tests should feel like contract examples:
shared-utilstests should describe pure inputs and outputs, edge cases, and formatting decisions.api-contractstests should parse representative unknown input and prove DTO/envelope contracts reject bad shapes.i18ntests should cover loader decisions, ICU/grammar behavior, strict-mode failure modes, and copy adapter outputs.
Prefer small fixture tables when a helper has several equivalent branches. Prefer explicit named cases when the branch teaches a domain rule.
Good shared-package entry points:
packages/api-contracts/src/types/api/__tests__/envelopes.test.tspackages/api-contracts/src/utils/__tests__/filtering.test.tspackages/i18n/src/core/utils/__tests__/resourceLoader.test.tspackages/i18n/src/server/utils/__tests__/serverErrorLocalization.test.tspackages/shared-utils/src/utils/__tests__/media.test.tsBrowser E2E Tests
Section titled “Browser E2E Tests”Browser E2E lives under apps/wavemap-front-end/test/e2e and uses Playwright. Test files should use
*.e2e.test.ts or *.e2e.test.tsx.
The current smoke suite is intentionally narrow and high-value:
- Deep-linked artist-details media flows against seeded dev data.
- Unauthenticated auth-routing smoke for protected-route redirect preservation and login page readiness.
The smoke suite manifest is apps/wavemap-front-end/test/e2e/config/suites.js, and the runner is
apps/wavemap-front-end/test/e2e/scripts/runPlaywrightSuite.js.
Keep suite membership explicit:
- CI smoke should call the package script and suite manifest instead of hard-coding spec paths in workflow YAML.
test:e2e:smokeis the CI-aligned narrow slice.test:e2eis the local broad browser iteration command.- Deployed browser routing, cold-start recovery, and media delivery suites are opt-in because they depend on shared deployed-dev state, runtime timing, or real app/API mutation.
- Cold-start and media-delivery suites require their explicit
PLAYWRIGHT_ENABLE_DEPLOYED_*flags so they do not run by accident from a broad localtest:e2ecommand. - Promote a browser spec into smoke only after its route, seed data, runtime setup, cleanup posture, and artifact value are stable enough to explain in one sentence.
Browser tests should be reserved for behavior that needs a real browser:
- Route redirects, browser focus, portal/dialog behavior, carousel interaction, media rendering, and deployed runtime recovery.
- High-value user journeys through implemented routes, not placeholder pages.
- Localized text only when copy is the behavior being asserted.
- Stable route constants, form names, test IDs, and page-object helpers when localization is incidental to the behavior.
Keep browser helpers thin and locator-focused. When duplication becomes real, create small test-info/page-object helpers near the suite instead of hiding product setup in a global harness.
Browser specs should read as journeys. The usual shape is:
const user = createAuthTestUser()const registrationPage = new RegistrationPageTestInfo(page)const loginPage = new LoginPageTestInfo(page)
await registrationPage.goto()await registrationPage.register(user)await loginPage.login(user)
await expect(page).toHaveURL("/en/dashboard")The page/test-info object should own selectors and repeated page actions. The spec should still own the story: which user exists, which route is visited, which visible state proves the journey, and which cleanup or generated artifact matters.
Avoid broad Playwright route mocking unless the product risk is specifically browser behavior around a network failure. If the mocked response is the whole point of the test, ask whether a front-end integration test would give a faster and clearer signal.
Good browser E2E entry points:
apps/wavemap-front-end/test/e2e/auth/register-verify-login-logout.e2e.test.tsapps/wavemap-front-end/test/e2e/auth/protected-route-login-redirect.e2e.test.tsapps/wavemap-front-end/test/e2e/artists/artist-details/artist-details.e2e.test.tsapps/wavemap-front-end/test/e2e/artists/artist-details/artist-media-delivery.e2e.test.tsapps/wavemap-front-end/test/e2e/deployed-dev/browser-routing-smoke.e2e.test.ts