Testing
This guide documents how we test the Frapp API using Jest and NestJS testing utilities.
1. Test types
We use three main test layers:
- Unit tests — services, guards, interceptors (mocking repositories and Supabase)
- Integration tests — hitting real endpoints in a running API against local Supabase
- E2E tests — supertest-based flows (e.g. auth → create chapter → add member)
All tests live under apps/api/src/** for unit tests and apps/api/test/** for E2E.
2. Jest setup
Jest is configured in apps/api/package.json with scripts:
npm run test— unit testsnpm run test:watch— watch modenpm run test:e2e— E2E tests (usestest/jest-e2e.json)
The typical Nest testing pattern:
import { Test } from "@nestjs/testing";
import { ChapterService } from "../chapter.service";
describe("ChapterService", () => {
it("creates a chapter", async () => {
const module = await Test.createTestingModule({
providers: [
ChapterService,
{ provide: "IChapterRepository", useValue: mockChapterRepo },
{ provide: "IRoleRepository", useValue: mockRoleRepo },
],
}).compile();
const service = module.get(ChapterService);
const chapter = await service.createChapter(/* dto */);
expect(chapter).toBeDefined();
});
});
3. Service tests
For each Phase 1 service (auth, user, chapter, member, rbac, invite) we aim to cover:
- Happy-path operations (create, update, list, etc.)
- Error cases (not found, forbidden, invalid state)
- Edge cases (duplicate creation, role changes, invite expiry)
Mocks:
- Repositories — plain objects with Jest mock functions (e.g.
jest.fn().mockResolvedValue(...)). - External adapters — mocked Stripe/Expo clients where relevant.
Keep business logic in services small and focused. This makes unit tests much easier to write and maintain.
4. Guards and interceptors
Guards to test:
SupabaseAuthGuard— valid vs. invalid/missing JWTChapterGuard— correctly accepts members of a chapter and rejects non-membersPermissionsGuard— honors wildcard (*) and specific permissions
Interceptors:
RequestIdInterceptor— attachesx-request-idwhen missing and forwards when present- Logging interceptor — ensures it logs request/response metadata (can be smoke-tested)
5. E2E scaffolding
E2E config file: apps/api/test/jest-e2e.json:
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}
Basic example (apps/api/test/app.e2e-spec.ts):
import request from "supertest";
import { INestApplication } from "@nestjs/common";
describe("Health (e2e)", () => {
let app: INestApplication;
beforeAll(async () => {
// create Nest application against real Supabase (local or staging)
});
it("/health (GET)", async () => {
const res = await request(app.getHttpServer()).get("/health");
expect(res.status).toBe(200);
});
});
6. Coverage expectations
For the API we aim for:
- Core services and guards — high coverage (happy path + key error paths)
- Integration/E2E — at least one end-to-end flow per major domain
Do not chase 100% coverage at the expense of meaningful tests. Focus on critical business rules, security boundaries, and regressions we've actually seen.