Developers
Testing
The test suites — PHPUnit functional tests, Vitest unit tests, Playwright end-to-end.
Tests live with the code: core keeps
three PHPUnit suites in tests/ — Unit, Functional and Integration
— alongside Foundry factories and stubs;
client keeps Vitest specs in
tests/unit/ and tests/components/, and the Playwright suite in
tests/e2e/. All commands below run inside the
DDEV environments.
Core — PHPUnit
ddev test # whole suite
ddev test --testsuite Unit # one suite (Unit · Functional · Integration)
ddev test tests/Functional/Api # one directory or file
ddev test --filter=testPinMessage # one method
ddev test prepares the test database on first run (schema, JWT keys) —
no manual setup. The functional suite drives the real HTTP kernel against
MariaDB; each test runs inside a transaction that’s rolled back afterwards
(DAMA), so tests are isolated and fast. Fixtures are built with Foundry
factories (tests/Factory/). External services are stubbed for the Unit
and Functional suites: no real Mercure publishes, in-memory search and
presence — so those two need only MariaDB. The Integration suite
deliberately runs against real Valkey/Redis, which is the only
automated coverage of the Redis-backed stores.
Static analysis and code style gate every change:
ddev composer phpstan # level 7
ddev composer checkcs # or fixcs to auto-fix
Client — Vitest (unit)
ddev npm test # or run test:watch / run test:coverage
These run fully in-process — no browser, no backend. tests/unit/ covers
pure logic (utils, reducers, cache patchers); tests/components/ renders
components against a mocked API layer. Flows that need a real server —
auth, realtime, permissions — are covered by E2E instead.
Client — Playwright (end-to-end)
ddev e2e # headless; resets core's test DB first
ddev npm run test:e2e:headed # watch the browser
ddev npm run test:e2e:ui # Playwright UI mode
The E2E suite needs both stacks running (core + client), and ddev setup run once in the client to write .env.local and fetch the
Playwright browser. ddev e2e resets core’s test database first (it calls
core’s reset-test-db command), then runs the specs in parallel workers —
each worker seeds its own isolated community and accounts, so they never
share mutable state.
E2E is the slowest suite — the usual loop is unit + typecheck + lint while iterating, one ddev e2e before a PR.