Skip to main content

Testing Guide

This project uses Vitest for all testing (API and frontend).

Use Node 24 locally (repo root .nvmrc). With nvm: nvm install and nvm use before running tests so your version matches CI.

Quick Start

# Run all tests
pnpm test

# Run API tests only
pnpm test:api

# Run web tests only
pnpm test:web

# Run tests in watch mode
pnpm test:watch

# Run tests with coverage
pnpm test:coverage

# Open Vitest UI
pnpm test:ui

Test Structure

API Tests (apps/api)

Location: src/**/*.spec.ts

Example:

import { describe, it, expect } from 'vitest';
import { MyService } from './my.service';

describe('MyService', () => {
it('should do something', () => {
expect(MyService).toBeDefined();
});
});

Web Tests (apps/web)

Location: src/**/*.test.{ts,tsx}

Example:

import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import { MyComponent } from './MyComponent';

describe('MyComponent', () => {
it('renders correctly', () => {
render(<MyComponent />);
expect(screen.getByText('Hello')).toBeInTheDocument();
});
});

Testing Stack

  • Test Runner: Vitest
  • React Testing: @testing-library/react
  • User Interactions: @testing-library/user-event
  • Assertions: @testing-library/jest-dom (matchers)

Best Practices

1. Test Organization

Group related tests using describe blocks:

describe('MyComponent', () => {
describe('when logged in', () => {
it('shows user menu', () => {});
});

describe('when logged out', () => {
it('shows login button', () => {});
});
});

2. Use Descriptive Test Names

// ❌ BAD
it('works', () => {});

// ✅ GOOD
it('should display error message when form is invalid', () => {});

3. Arrange, Act, Assert

it('should add two numbers', () => {
// Arrange
const a = 1;
const b = 2;

// Act
const result = add(a, b);

// Assert
expect(result).toBe(3);
});

4. Mock External Dependencies

import { vi } from 'vitest';

const mockFetch = vi.fn();
global.fetch = mockFetch;

it('fetches data', async () => {
mockFetch.mockResolvedValue({ json: () => ({ data: 'test' }) });
// Test implementation
});

5. Test User Behavior, Not Implementation

// ❌ BAD - testing implementation
expect(component.state.isOpen).toBe(true);

// ✅ GOOD - testing behavior
expect(screen.getByRole('dialog')).toBeVisible();

CI/CD Integration

Tests run automatically on:

  • Push to master, main, or develop branches
  • Pull requests to these branches

CI Pipeline

  • Unit tests – Fast job, no services required
  • Coverage – Runs after unit tests pass; uploads to Codecov

There is currently no API e2e job. The previous suite ran on Jest, which cannot load TypeScript 7, so it was removed when the workspace unified on TS7. When e2e coverage is rebuilt on Vitest it will run as a non-gating job so it never blocks merges or deploys.

See .github/workflows/test.yml for configuration.

Coverage

Coverage reports are generated in:

  • apps/api/coverage/
  • apps/web/coverage/

View HTML coverage reports:

# API
open apps/api/coverage/index.html

# Web
open apps/web/coverage/index.html

Common Testing Patterns

Testing React Components

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

it('handles user interaction', async () => {
const user = userEvent.setup();
render(<Button onClick={mockFn}>Click me</Button>);

await user.click(screen.getByRole('button'));
expect(mockFn).toHaveBeenCalled();
});

Testing Async Code

it('fetches data', async () => {
const promise = fetchData();
expect(promise).toBeInstanceOf(Promise);

const data = await promise;
expect(data).toEqual({ id: 1 });
});

Testing Errors

it('throws error for invalid input', () => {
expect(() => divide(10, 0)).toThrow('Division by zero');
});

Troubleshooting

Tests Run Slowly

  • Use test.concurrent for independent tests
  • Mock expensive operations
  • Reduce test data size

Flaky Tests

  • Avoid hardcoded timeouts
  • Use waitFor for async operations
  • Mock date/time functions

Import Errors

  • Check vitest.config.ts for path aliases
  • Ensure dependencies are installed
  • Verify TypeScript configuration

Resources