Testing
Sessiq has four kinds of automated tests. Unit tests need no database. Integration and API end-to-end tests run against a dedicated test database. The Playwright suite drives the web app in a real browser.
| Suite | Runner | Location | File pattern | Command |
|---|---|---|---|---|
| API unit | Vitest | apps/api/tests/unit/ | *.spec.ts | pnpm test:api:unit |
| API integration | Jest | apps/api/tests/integration/ | *.int.spec.ts | pnpm test:api:int |
| API end-to-end | Jest | apps/api/tests/e2e/ | *.e2e.spec.ts | pnpm test:api:e2e |
| Browser (QA) | Playwright | apps/web/e2e/specs/ | *.spec.ts | pnpm test:qa |
Other suites:
- Web unit tests use Jest:
pnpm -F web test. - The scheduling engine has its own tests:
pnpm -F @aetech/scheduling-engine run test.
The repository's TESTING_GUIDE.md has the detailed conventions.
Unit tests
pnpm test:api:unit
Unit tests run with no database (SKIP_DB=1). They mock the database layer.
To mock tenant-scoped database access, use createMockRlsContext() from apps/api/tests/helpers/mock-rls-context.ts. It replaces @booking/db/rls-context so that withTenant() passes a mocked transaction client to your callback:
import { vi } from 'vitest';
import { createMockRlsContext } from '../../helpers/mock-rls-context';
vi.mock('@booking/db/rls-context', () => createMockRlsContext());
When a mock does not resolve, look for the cause first: __mocks__ directories, the shared helpers in apps/api/tests/helpers/ and apps/api/tests/setup/, and the import chain.
Integration tests
Integration tests use a separate PostgreSQL container on port 5433. Set it up once:
docker compose --profile test up -d postgres_test
pnpm exec dotenv -e .env.test -- pnpm db:test:reset
- The container bootstraps its roles, extensions and tenant functions from
packages/db/scripts/bootstrap-test-db.sqlon first start. pnpm db:test:resetsyncs the Prisma schema and applies the custom SQL. It needsDATABASE_URLpointing at the test database, which.env.testprovides. It is idempotent; re-run it whenever the schema changes.- If
bootstrap-test-db.sqlchanges, recreate the volume:docker compose --profile test down -v, then start the container again.
Then run the suite:
pnpm test:api:int
The runner reads .env.test, runs files in batches and retries batches that fail on connection errors. If the schema is out of date, the tests stop with a hint to run pnpm db:test:reset.
Naming
Every integration test file must end in .int.spec.ts. The runner only picks up that suffix; files named .integration.spec.ts are silently skipped.
Building the module graph
Compose the NestJS app with createTestApp() from apps/api/tests/setup/test-app.ts. It returns a TestingModuleBuilder built from TestAppModule, which mirrors the production AppModule minus the global guard, pipe and filter providers. It also sets permissive default mocks for JwtAuthGuard, TenantGuard, SiteAccessGuard and RbacGuard.
import { createTestApp } from '../../setup/test-app';
import { createIntegrationTestApp } from '../../setup/integration-helpers';
const moduleRef = await createTestApp()
.overrideGuard(JwtAuthGuard)
.useValue(myJwtMock)
.compile();
const app = createIntegrationTestApp(moduleRef);
await app.init();
Chain .overrideGuard(...) and .overrideProvider(...) for per-test behaviour. Do not hand-list modules or build your own preset; importing the real module graph keeps tests in step with production.
Because the global guards are stripped, a test cannot prove that a route is protected by them. Assert the route's metadata (for example that it is not marked public) instead.
API end-to-end tests
pnpm test:api:e2e
These use the same test database and createTestApp(), and exercise complete flows such as customer sign-in and payments through the HTTP layer.
Browser tests (Playwright)
The Playwright suite in apps/web/e2e/specs/ runs against a running web app and API (by default http://localhost:3000, override with BASE_URL).
| Command | Runs |
|---|---|
pnpm test:qa | The whole suite |
pnpm test:qa:smoke | Tests tagged @smoke |
pnpm test:qa:payment | Tests tagged @payment |
pnpm test:qa:mobile | Tests tagged @mobile |
pnpm test:qa:a11y | Tests tagged @accessibility |
Locally only the chromium-light project runs. CI runs every project (Chromium light and dark, Firefox, WebKit, mobile Chrome, mobile Safari, high contrast). Pick one locally with pnpm test:qa -- --project=mobile-chrome.
Before you write a browser test:
- Take selector text from
apps/web/messages/en.jsonandfi.json, not from memory. - Check the exact API paths in the controller decorators or
apps/api/openapi.json. - Check rate limits on the endpoints you call, and the permissions the test user's role has.
- Keep screenshots and scripts you want to keep outside
apps/web/e2e/test-results/; it is cleared on every run.
Types in tests
All test files are typechecked by pnpm typecheck. Fix type errors in tests like any other code. Do not add // @ts-nocheck.