Skip to main content

Testing

CivicPulse has a two-tier testing strategy: Vitest for the React frontend and Jest + Supertest for the Node.js backend. Both suites run in CI before every deployment.


Frontend Tests (Vitest)​

The frontend uses Vitest with jsdom and React Testing Library.

Run Tests​

cd frontend

npm test # single run with coverage
npm run test:watch # watch mode (re-runs on save)
npm run test:coverage # detailed coverage report

Test Files​

FileTestsWhat it covers
src/__tests__/AdminLogin.test.jsx8Login form UI, success/error/403 flows, auto-redirect
src/__tests__/useFlags.test.js6Flag defaults, localStorage, server merge, fetch failure
src/__tests__/DonorWall.test.jsx5Donor data structure, showProfile, image handling

Setup​

Vitest is configured in vite.config.js:

test: {
environment: 'jsdom',
globals: true,
setupFiles: './src/__tests__/setup.js',
}

setup.js provides:

  • Global fetch mock via vi.fn()
  • sessionStorage and localStorage mocks
  • @testing-library/jest-dom matchers

Example β€” AdminLogin​

it('shows no-access screen on 403', async () => {
mockApi.login.mockRejectedValue({ status: 403 });
render(<AdminLogin />);
await userEvent.type(screen.getByLabelText(/email/i), 'x@x.com');
await userEvent.type(screen.getByLabelText(/password/i), 'wrong');
await userEvent.click(screen.getByRole('button', { name: /sign in/i }));
expect(await screen.findByText(/do not have access/i)).toBeInTheDocument();
});

Backend Tests (Jest + Supertest)​

The backend uses Jest with Supertest for HTTP-layer testing. DynamoDB is fully mocked β€” no real AWS calls are made.

Run Tests​

cd backend

npm test # single run with coverage

Test Files​

FileTestsWhat it covers
tests/admin.auth.test.js12Login, JWT issuance/verification, password validation, duplicate email
tests/admin.config.test.js8Feature flags defaults/merge, donation config filtering
tests/donors.test.js10Twitter API fetch, donor CRUD, public list filtering
tests/issues.resolution.test.js26Resolve with proof media, community rating, reopen request flow
tests/xService.test.js21X_HANDLE config, detectCategory/City, fetchMentions demo/live/error modes
tests/whatsappService.test.js26WA_PHONE_NUMBER_ID, detectCategory/City, maskPhone, webhook ingestion, demo/live/fallback
tests/routes.whatsapp.test.js17HTTP layer: webhook verification, HMAC signature, message feed, convert auth+validation
tests/routes.x.test.js9HTTP layer: mentions feed shape, demo mode, convert auth+validation

Total: 120 tests across 8 suites β€” all green.

Test Environment Variables​

The test runner needs these env vars set (via shell or .env.test):

ADMIN_JWT_SECRET=test-secret-at-least-32-characters-long
AWS_REGION=us-east-1
# No real AWS credentials needed β€” DynamoDB is mocked

Mocking DynamoDB​

Each test file mocks ../config/dynamodb before requiring application code:

jest.mock('../config/dynamodb', () => ({
docClient: { send: jest.fn() },
TABLES: {
ADMINS: 'civicpulse-admins',
SITE_CONFIG: 'civicpulse-site-config',
DONORS: 'civicpulse-donors',
},
}));

Example β€” Admin Auth​

it('rejects wrong password with 401', async () => {
mockDocClient.send.mockResolvedValueOnce({
Item: { ...adminRecord, passwordHash: await bcrypt.hash('correct', 4) },
});
const res = await request(app)
.post('/api/admin/auth/login')
.send({ email: 'admin@test.com', password: 'wrong-password' });
expect(res.status).toBe(401);
});

Coverage Thresholds​

LayerThresholdNotes
Frontendβ€”No hard threshold; tracked in CI output
Backend10% linesMinimum gate; new service files should target 80%+

Coverage reports are written to:

  • frontend/coverage/ β€” lcov + html
  • backend/coverage/ β€” lcov + html

CI Integration​

Tests run automatically in the test job in .github/workflows/deploy.yml before any deployment:

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: cd frontend && npm ci && npm test
- run: cd backend && npm ci && npm test
env:
ADMIN_JWT_SECRET: ${{ secrets.ADMIN_JWT_SECRET }}
AWS_REGION: us-east-1
- run: cd frontend && npm run build # verify build doesn't break

The deploy-backend and deploy-frontend jobs both need: [test] β€” a failing test blocks deployment.


Adding New Tests​

Frontend​

  1. Create src/__tests__/MyComponent.test.jsx
  2. Import from @testing-library/react and vitest
  3. Mock external modules with vi.mock('../path/to/module')

Backend​

  1. Create tests/my-service.test.js
  2. Add jest.mock('../config/dynamodb', ...) at the top
  3. Set required env vars in beforeAll
  4. Use supertest(app) for HTTP tests or import service functions directly for unit tests