Running Days Docs
GitHub

Testing Guide

Running Days uses Vitest for unit tests and Playwright for end-to-end tests.

Test Structure

text
packages/
β”œβ”€β”€ business-logic/
β”‚   └── src/
β”‚       β”œβ”€β”€ streak-calculator.ts
β”‚       └── streak-calculator.test.ts  ← Co-located
β”œβ”€β”€ database/
β”‚   └── src/
β”‚       └── encryption.test.ts
└── utils/
    └── src/
        └── date-utils.test.ts

apps/
β”œβ”€β”€ api/
β”‚   └── tests/
β”‚       β”œβ”€β”€ auth.test.ts
β”‚       └── cache.test.ts
└── web/
    └── e2e/
        β”œβ”€β”€ auth.spec.ts
        └── dashboard.spec.ts

Running Tests

All Tests

bash
pnpm test

Specific Package

bash
pnpm --filter @running-days/business-logic test
pnpm --filter @running-days/database test

Watch Mode

bash
pnpm --filter @running-days/business-logic test:watch

With Coverage

bash
pnpm test:coverage

Writing Unit Tests

Basic Test

typescript
import { describe, it, expect } from 'vitest';
import { calculateStreaks } from './streak-calculator.js';

describe('calculateStreaks', () => {
  it('should return 0 for empty input', () => {
    const result = calculateStreaks([]);
    expect(result.current).toBe(0);
  });
});

Testing with Mocked Time

typescript
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';

describe('time-sensitive tests', () => {
  beforeEach(() => {
    vi.useFakeTimers();
    vi.setSystemTime(new Date('2024-01-15T12:00:00Z'));
  });

  afterEach(() => {
    vi.useRealTimers();
  });

  it('should calculate based on current date', () => {
    // Test runs as if it's Jan 15, 2024
  });
});

Testing Async Code

typescript
it('should fetch data', async () => {
  const result = await fetchWorkouts();
  expect(result).toHaveLength(5);
});

E2E Tests with Playwright

Running E2E Tests

bash
# All E2E tests
pnpm --filter @running-days/web test:e2e

# Specific test file
pnpm --filter @running-days/web test:e2e auth.spec.ts

# With UI mode
pnpm --filter @running-days/web test:e2e --ui

Writing E2E Tests

typescript
import { test, expect } from '@playwright/test';

test('user can view dashboard', async ({ page }) => {
  await page.goto('/dashboard');
  await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
});

Authentication in E2E

Use the mock API client for authenticated tests:

typescript
test('authenticated dashboard shows workouts', async ({ page }) => {
  // Mock auth is handled by test fixtures
  await page.goto('/dashboard');
  await expect(page.getByTestId('workout-list')).toBeVisible();
});

Test Best Practices

Do

  • Test behavior, not implementation
  • Use descriptive test names
  • Keep tests focused and isolated
  • Test edge cases and error paths
  • Use realistic test data

Don’t

  • Don’t test framework code
  • Don’t mock everything
  • Don’t write flaky tests
  • Don’t skip failing tests permanently

Coverage Requirements

Aim for these coverage levels:

PackageTarget
business-logic90%
database80%
api75%
web70% (E2E covers UI)

CI Integration

Tests run automatically on:

  • Pull request creation
  • Push to main branch
  • Release tagging

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