Docs/Guide

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 tests
  • npm run test:watch — watch mode
  • npm run test:e2e — E2E tests (uses test/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.
Tip

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 JWT
  • ChapterGuard — correctly accepts members of a chapter and rejects non-members
  • PermissionsGuard — honors wildcard (*) and specific permissions

Interceptors:

  • RequestIdInterceptor — attaches x-request-id when 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
Warning

Do not chase 100% coverage at the expense of meaningful tests. Focus on critical business rules, security boundaries, and regressions we've actually seen.