How Universal Healthcare Data Network is tested — runners, fixtures, scope of coverage, and what CI runs.
Back to README · Architecture · Contributing · Environment
# everything (Turbo runs only what your changes touched + downstream)
pnpm test
# a single package
pnpm --filter @universal-healthcare/api test
pnpm --filter @universal-healthcare/web test
pnpm --filter @universal-healthcare/mobile test
pnpm --filter @universal-healthcare/shared testpnpm test runs through Turbo, so a change to packages/shared cascades into the test runs for apps/api and apps/web automatically.
| Package | Runner | Database / Env | Scope |
|---|---|---|---|
api |
Vitest + Supertest | SQLite via apps/api/.env.test |
Module integration through the Express app — no real network port |
web |
Vitest + Testing Library + jsdom | None (mocked lib/auth-client) |
Render pages inside AuthProvider, assert fields + client errors |
mobile |
Jest (jest-expo) + Testing Library RN |
None | Smoke + screen / hook / image-picker tests |
shared |
Vitest | None | Schema validation rules (Zod) |
- Runner: Vitest + Supertest. Tests use
createApp()fromsrc/app.ts— no real HTTP listener is started. - Database: A throwaway SQLite database backed by
apps/api/.env.test. Thetestscript runsprisma db push --skip-generate --accept-data-lossagainst it before Vitest, and a globalbeforeEachintests/setup.tsclears tables between tests so tests are order-independent. - File parallelism:
fileParallelism: falseto dodge SQLite lock contention. Tests run sequentially within the package, but parallel across packages (Turbo handles it). - Coverage focus:
auth/register— success, duplicate email, invalid inputauth/login— success, invalid credentials, unknown accountrequireAuthmiddleware — valid token, missing token, invalid/expired tokenusers/mePATCH — zod validation paths and persistence
- Create
src/modules/<domain>/tests/<thing>.test.ts - Import
createAppand use Supertest againstrequest(app) - For DB-touching tests,
beforeEachshould reset affected tables (or rely on the global hook intests/setup.ts) - Assert on response status and on response shape — never the entire JSON object
- Runner: Vitest +
@testing-library/react+ jsdom - Pattern: Each page test renders the route inside
<AuthProvider>and mockslib/auth-client(and friends) so no real network call is made. - Coverage focus:
/loginand/register— fields render and client-side schema errors surface/profile/edit— initial values populate fromuseAuth()+getMe/creators/[slug]— loading / not-found / ok states
The web app uses @universal-healthcare/shared for client-side validation. If the API schema rejects something, the form rejects the same thing earlier — which means any upgrade in validation lives in one place. Tests assert that the matching behaviour holds.
- Runner: Jest with
jest-expopreset and@testing-library/react-native - Existing tests: A smoke test renders
Appand asserts it mounts; component tests forProfileImagePickerandCreatorProfileScreen; hook tests foruseImagePicker. - Coverage focus:
- Permission grants / denials flow through
useImagePicker CreatorProfileScreenswitches between loading / error / ok states- EXPO_PUBLIC_API_URL is read from
process.envat runtime
- Permission grants / denials flow through
- Runner: Vitest
- Coverage focus: Validation rules for
loginSchema,registerSchema,updateMeSchema— valid emails / invalid emails / password rules / required fields / missing fields / extra fields.
Because packages/shared is consumed directly from source, a test failure here will surface in api, web, and mobile test runs.
CI mirrors local. Each package has its own workflow under .github/workflows/ that installs dependencies and runs the relevant turbo run tasks scoped to that package with --filter:
| Workflow file | Tasks |
|---|---|
api.yml |
lint · test · build |
web.yml |
lint · test · build |
mobile.yml |
lint · test |
shared.yml |
lint · build |
stellar.yml |
lint · build |
A pull request only needs to pass the workflows for the packages it touches. Turbo's dependency graph cascades downstream — a change to packages/shared will trigger api, web, and mobile tests in addition to shared.
We don't chase 100%. We chase the right lines:
- Every module has tests for its happy path, its validation failure, and its forbidden-cross-module-access case.
- Shared Zod schemas are exhaustively tested because they're a contract — one bad branch silently corrupts web + mobile + api at the same time.
- UI tests focus on state transitions (loading / error / empty / ok), not on asserting pixel layouts.
- Don't test the framework — test the behaviour you wrote. If you find yourself asserting that
useStatetriggers a re-render, stop.
When in doubt: cover the path a real user can hit, and the path a real attacker can break.