Testing
Philosophy
- Black-box. Assert on inputs and outputs, not internals. A refactor that doesn't change behavior shouldn't break tests.
- No mocks unless necessary. Prefer real implementations (real DB via
vitest-pool-workers, real HTTP). Only mock external services we don't control (Bridge). - Focus on I/O boundaries. API request/response, function I/O, component render + interaction. Don't test private functions or intermediate state.
- Non-brittle assertions. Don't snapshot markup or match exact error strings.
What runs where
Running tests
# All packages
pnpm test
# One package
pnpm --filter @gringo-pay/api test
pnpm --filter @gringo-pay/mobile test
# Coverage
pnpm test:coverage
# Mobile E2E (needs a running dev build + Maestro CLI)
pnpm test:e2e
API tests
- Runner: Vitest +
@cloudflare/vitest-pool-workers. - Fixtures in
apps/api/src/test/:app.ts—createTestApp()returns the Hono app + a scoped DB.client.ts—TestClientwraps requests with the required Privy JWT header.factories.ts—seedUser(),seedWallet(), etc.bridge-mock.ts— mock Bridge API responses.
- Requires
TEST_NEON_DB_URLto be set (see setup). CI hard-fails if it's unset.
Colocation: apps/api/src/pix/routes.test.ts lives next to apps/api/src/pix/routes.ts.
Mobile tests
- Runner: Jest with
jest-expopreset +@testing-library/react-native. - Metro's
blockListexcludes*.test.*from bundles.
E2E (Maestro)
- Scenarios in
maestro/*.yaml(e.g.maestro/smoke.yaml). - Run against a running dev build (iOS simulator or Android emulator).
- Not currently blocking in CI — runs as part of the CD mobile preview build.
Coverage
Coverage reports (Istanbul for API, V8 elsewhere) are written to <pkg>/coverage/ and uploaded as a CI artifact (coverage-reports, 14-day retention).