Testing
Moonlight uses Jest for both unit and E2E testing.
Test Database
A separate PostgreSQL instance runs on port 9433 for tests. It uses the same Flyway migrations as the main database.
# Start test database and Pub/Sub/storage dependencies
docker-compose up moon_test_db flyway-test pubsub minio minio-init
Running Tests
Unit Tests
cd moonlight-server
npm run test # Run once
npm run test:watch # Watch mode
npm run test:cov # With coverage
E2E Tests
cd moonlight-server
npm run test:e2e
E2E tests require infrastructure to be running (test database, Pub/Sub emulator, MinIO).
createTestApp() sets PUBSUB_EMULATOR_HOST (default localhost:8085) and uses project moonlight-test. Bootstrap automatically creates the required topics and subscriptions in the emulator on every app.init(). If the Pub/Sub emulator is not running, E2E suites will fail to start because messaging providers cannot connect to it.
The messaging.e2e-spec.ts suite covers:
IdempotencyServiceexactly-once semantics and TTL cleanup- Round-trip publish/subscribe through the emulator using
PubSubPublisher
E2E Test Structure
moonlight-server/test/
├── helpers/
│ ├── test-app.ts # Test application setup
│ └── test-data.ts # Factories and cleanup
├── entry-lifecycle.e2e-spec.ts
├── document-workflow.e2e-spec.ts
├── void-refund-reimbursement.e2e-spec.ts
└── accounting.e2e-spec.ts
Test Suites
Entry Lifecycle
- Create entries (INCOME/EXPENSE)
- List and filter entries
- Update entry status
- Verify invalid status transitions are rejected
- Verify audit trail
- Check completeness tracking
Document Workflow
- Upload BASIS and PAYMENT_PROOF documents
- Accountant requests documents
- Fulfill document requests
- Approve/reject documents
- Verify completeness after approval
Void, Refund & Reimbursement
- Void an entry
- Create full and partial refunds
- Verify original entry is voided only after a full refund
- Verify partial refunds do not void the original entry
- Reject refunds larger than the original amount
- Create employee reimbursements
- Reject reimbursements larger than the original expense amount
- Track related entries
- Verify audit events
Accounting Connections
- Create/list/update accounting connections
- Deactivate connections
- Sync status tracking
- Webhook handling
CI Expectations
The backend pull request pipeline is expected to run:
npm run lintnpm run testnpm run test:e2enpm run build
Test Helpers
createTestApp()
Creates a full NestJS application configured for the test database.
cleanDatabase(ds)
Truncates all tables for a clean test state.
seedBaseData(ds)
Creates minimal required data: workspace, project, parties, entry source.
createTestUser(ds, jwtService, options?)
Creates a user with owner permissions and returns a JWT token.
createTestEntry(ds, overrides?)
Creates an entry with optional field overrides.
Writing New Tests
import { TestContext, createTestApp, closeTestApp } from './helpers/test-app';
import { cleanDatabase, seedBaseData, createTestUser } from './helpers/test-data';
describe('My Feature (e2e)', () => {
let ctx: TestContext;
let userToken: string;
beforeAll(async () => {
ctx = await createTestApp();
await cleanDatabase(ctx.dataSource);
await seedBaseData(ctx.dataSource);
const user = await createTestUser(ctx.dataSource, ctx.jwtService);
userToken = user.token;
});
afterAll(async () => {
await cleanDatabase(ctx.dataSource);
await closeTestApp(ctx);
});
it('should do something', async () => {
const res = await request(ctx.app.getHttpServer())
.get('/my-endpoint')
.set('Authorization', `Bearer ${userToken}`)
.expect(200);
expect(res.body).toMatchObject({ ... });
});
});
Related Pages
- Development Setup — how to start infrastructure and test database
- Architecture — backend module structure and key patterns