Skip to content

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.

Test each concern at its lowest meaningful layer exactly once. Duplicate coverage only when the second layer protects a different failure mode.

LayerUse In Context OfAvoid Using It For
Unit testsLeaf components, pure helpers, utilities, narrow branching behavior.Proving app provider wiring, route behavior, persistence, or browser UX.
Front-end integrationComponents plus providers, route state, auth state, query clients, hydration, DOM effects.Exhaustive browser coverage or backend persistence behavior.
Client network/API testsAxios envelope handling, retry behavior, token refresh, redirects, request adapters.Re-testing backend handler internals.
Backend request testsHono route wiring, middleware composition, request validation, envelope shape, branching.Real database mutation outcomes or provider-specific object-store behavior.
DB-backed integrationPersistence behavior, query shape, relational correctness, transaction rollback, auth DB chains.Schema validation branches that fail before the DB is touched.
Adapter testsExternal provider behavior, path construction, upload/delete semantics, URL resolution.Ordinary app-logic paths where a storage double gives the same signal.
Browser E2EReal browser flows through implemented surfaces and high-value user journeys.Copy-only permutations, placeholder pages, or broad regression coverage.
Deployed smokeThe selected deployed target, cloud state, runtime config, artifacts, and public URL.Local correctness or pre-deploy repo health.

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.

Prefer the narrowest command that proves the boundary you changed.

Root commands:

Terminal window
pnpm test
pnpm test:packages
pnpm test:unit
pnpm test:watch
pnpm test:coverage
pnpm test:ci
pnpm test:e2e:prepare
pnpm test:e2e:verify-source-runtime
pnpm format:i18n
pnpm verify:i18n
pnpm verify:scripts
pnpm verify:github-actions
pnpm build:docs
pnpm smoke:docs

Current behavior:

  • pnpm test and pnpm test:ci run the shared-package Vitest suites, then the front-end Vitest suite.
  • pnpm test:packages runs @wavemap/shared-utils, @wavemap/api-contracts, and @wavemap/i18n.
  • pnpm test:unit runs only the front-end Vitest suite.
  • Browser E2E is intentionally outside the root aggregate test command.
  • pnpm test:e2e:prepare builds workspace package outputs that front-end Playwright source imports need.
  • pnpm test:e2e:verify-source-runtime prepares those package outputs, then verifies Playwright source-runtime import and discovery behavior.
  • pnpm -C apps/wavemap-front-end test:e2e:smoke runs the explicit smoke suite manifest, not every browser spec.
  • pnpm -C apps/wavemap-front-end test:e2e runs all currently discovered front-end browser specs for local iteration.

Front-end commands:

Terminal window
pnpm -C apps/wavemap-front-end test
pnpm -C apps/wavemap-front-end test:watch
pnpm -C apps/wavemap-front-end test:coverage
pnpm -C apps/wavemap-front-end test:ci
pnpm -C apps/wavemap-front-end test:e2e
pnpm -C apps/wavemap-front-end test:e2e:smoke
pnpm -C apps/wavemap-front-end test:e2e:deployed-browser-routing
pnpm -C apps/wavemap-front-end test:e2e:deployed-cold-start
pnpm -C apps/wavemap-front-end test:e2e:deployed-media-delivery
pnpm -C apps/wavemap-front-end test:e2e:headed
pnpm -C apps/wavemap-front-end test:e2e:ui
pnpm -C apps/wavemap-front-end exec playwright install chromium

Back-end commands:

Terminal window
pnpm -C apps/wavemap-back-end test
pnpm -C apps/wavemap-back-end test:watch
pnpm -C apps/wavemap-back-end test:integration
pnpm -C apps/wavemap-back-end test:ci
pnpm -C apps/wavemap-back-end test:db-backed
pnpm -C apps/wavemap-back-end test:db-backed:s3-emulator
pnpm -C apps/wavemap-back-end test:db-backed:watch

Shared-package commands:

Terminal window
pnpm -C packages/shared-utils test
pnpm -C packages/api-contracts test
pnpm -C packages/i18n test

Docs commands:

Terminal window
pnpm build:docs
pnpm smoke:docs

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.

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.tsx
apps/wavemap-front-end/src/components/FileUploadSurface/__tests__/FileUploadSurface.test.tsx
apps/wavemap-front-end/src/components/Carousel/__tests__/Carousel.test.tsx
apps/wavemap-front-end/src/components/TimePicker/__tests__/TimePicker.test.tsx
apps/wavemap-front-end/src/utils/media/__tests__/mediaDraft.test.ts

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.tsx
apps/wavemap-front-end/src/app/[locale]/(auth)/login/InternalComponents/Login/__tests__/Login.integration.test.tsx
apps/wavemap-front-end/src/utils/auth/__tests__/useRequireAuth.integration.test.tsx
apps/wavemap-front-end/src/app/[locale]/(artists)/artist/[artistID]/InternalComponents/ArtistDetails/__tests__/ArtistDetails.integration.test.tsx
apps/wavemap-front-end/src/components/Forms/AddOrEditArtistForm/__tests__/AddOrEditArtistForm.integration.test.tsx

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.ts
apps/wavemap-front-end/src/api/axios/__tests__/interceptors.test.ts
apps/wavemap-front-end/src/api/axios/__tests__/auth/auth.test.ts
apps/wavemap-front-end/src/api/mutations/__tests__/auth/useLogin.integration.test.tsx
apps/wavemap-front-end/src/api/queries/__tests__/artists/useGetArtists.integration.test.tsx

The backend has two package-local Vitest suites.

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.ts
apps/wavemap-back-end/test/mocked-requests/__tests__/auth/login.route.test.ts
apps/wavemap-back-end/test/mocked-requests/__tests__/artists/createArtist.route.test.ts
apps/wavemap-back-end/test/mocked-requests/__tests__/users/pageQueryPresetsImportOnboarding.route.test.ts
apps/wavemap-back-end/test/mocked-requests/__tests__/contracts/routeMounts.test.ts

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.ts
apps/wavemap-back-end/test/db-backed/__tests__/artists/artistMedia.s3-emulator.route.test.ts
apps/wavemap-back-end/test/db-backed/__tests__/auth/registration.route.test.ts
apps/wavemap-back-end/test/db-backed/__tests__/auth/login.route.test.ts
apps/wavemap-back-end/test/db-backed/__tests__/pageQueryPresets/pageQueryPresets.route.test.ts

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.ts
apps/wavemap-back-end/src/handlers/artists/__tests__/deleteArtistMedia.test.ts
apps/wavemap-back-end/src/handlers/artists/__tests__/syncArtistMedia.test.ts
apps/wavemap-back-end/src/handlers/artists/__tests__/updateArtist.test.ts
apps/wavemap-back-end/src/utils/__tests__/mediaStorageRuntimeConfig.test.ts

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.ts
apps/wavemap-back-end/src/utils/__tests__/mockedMediaStorageAdapter.test.ts
apps/wavemap-back-end/src/utils/__tests__/mediaStorage.test.ts
apps/wavemap-back-end/src/adapters/DBtoAPI/artists/__tests__/artistMedia.test.ts
apps/wavemap-back-end/src/adapters/DBtoAPI/artists/__tests__/artistDetails.test.ts

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-utils tests should describe pure inputs and outputs, edge cases, and formatting decisions.
  • api-contracts tests should parse representative unknown input and prove DTO/envelope contracts reject bad shapes.
  • i18n tests 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.ts
packages/api-contracts/src/utils/__tests__/filtering.test.ts
packages/i18n/src/core/utils/__tests__/resourceLoader.test.ts
packages/i18n/src/server/utils/__tests__/serverErrorLocalization.test.ts
packages/shared-utils/src/utils/__tests__/media.test.ts

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:smoke is the CI-aligned narrow slice.
  • test:e2e is 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 local test:e2e command.
  • 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.ts
apps/wavemap-front-end/test/e2e/auth/protected-route-login-redirect.e2e.test.ts
apps/wavemap-front-end/test/e2e/artists/artist-details/artist-details.e2e.test.ts
apps/wavemap-front-end/test/e2e/artists/artist-details/artist-media-delivery.e2e.test.ts
apps/wavemap-front-end/test/e2e/deployed-dev/browser-routing-smoke.e2e.test.ts