Skip to main content

Running Tests

DevDaily uses Vitest for fast, modern testing.
npm test
Tests run automatically in CI across Node.js versions 18, 20, and 22 on every pull request.

Test Structure

All tests live in the tests/ directory and mirror the source structure:
tests/
├── auto-snapshot.test.ts       # 74 tests
├── commitlint.test.ts          # 6 tests
├── context-analyzer.test.ts    # 22 tests
├── copilot.test.ts             # 38 tests
├── git-analyzer.test.ts        # 15 tests
├── notifications.test.ts       # 70 tests
├── pr-creation.test.ts         # 123 tests
├── pr-prompt.test.ts           # 26 tests
├── pr-template.test.ts         # 25 tests
├── project-management.test.ts  # 130 tests
├── standup-context.test.ts     # 70 tests
├── ui.test.ts                  # 2 tests
└── work-journal.test.ts        # 123 tests
ModuleTestsDescription
auto-snapshot74Side-effect snapshots, hooks, config
work-journal123Persistent storage, search, aggregation
project-management130Jira, Linear, GitHub Issues integration
standup-context70Context building, formatting
notifications70Slack/Discord webhooks, output formatter
pr-creation123PR generation pipeline
copilot38AI prompt building
pr-prompt26Custom prompt file loading
pr-template25Template detection and parsing
context-analyzer22Work pattern analysis
git-analyzer15Git operations
commitlint6Conventional commit parsing
ui2UI rendering
Total724

Writing Tests

Test Conventions

1

Place tests in tests/ directory

Create files with .test.ts suffix
2

Use descriptive names

describe and it blocks should clearly explain what’s being tested
3

Mock external dependencies

Tests must not require network access, git repos, or API keys
4

Test happy path and edge cases

Cover both successful execution and error scenarios

Basic Test Example

import { describe, it, expect, vi } from 'vitest';
import { formatOutput } from '../src/utils/formatter.js';

describe('formatOutput', () => {
  describe('plain format', () => {
    it('strips markdown header markers', () => {
      // Arrange
      const input = '## Completed\n\n- Task A';
      
      // Act
      const result = formatOutput(input, 'plain');
      
      // Assert
      expect(result.text).not.toContain('##');
      expect(result.text).toContain('Completed:');
    });

    it('preserves the original markdown in .raw', () => {
      const md = '## Title\n\n**Bold text**';
      const result = formatOutput(md, 'plain');

      expect(result.raw).toBe(md);
    });
  });
});
Follow the Arrange-Act-Assert pattern for clear, readable tests.

Mocking

Config Mocking

Most tests mock the config module to avoid file system dependencies:
import { vi, beforeEach } from 'vitest';

const mockConfig = {
  output: { 
    format: 'markdown', 
    copyToClipboard: true, 
    showStats: true, 
    verbose: false 
  },
  git: { 
    defaultBranch: 'main', 
    excludeAuthors: [], 
    excludePatterns: [] 
  },
  standup: {
    defaultDays: 1,
    sections: ['completed', 'in-progress', 'blockers']
  }
};

vi.mock('../src/config/index.js', () => ({
  getConfig: () => mockConfig,
  getSecrets: () => ({}),
}));

beforeEach(() => {
  vi.clearAllMocks();
});

Network Request Mocking

For webhook and API tests, stub the global fetch:
import { vi, beforeEach, afterEach } from 'vitest';

const fetchMock = vi.fn();

beforeEach(() => {
  vi.stubGlobal('fetch', fetchMock);
  fetchMock.mockReset();
});

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

it('sends webhook to Slack', async () => {
  fetchMock.mockResolvedValueOnce({
    ok: true,
    status: 200,
  });

  await sendNotification({ /* ... */ });

  expect(fetchMock).toHaveBeenCalledWith(
    expect.stringContaining('slack.com'),
    expect.objectContaining({
      method: 'POST',
    })
  );
});

Git Operations Mocking

For tests involving git operations:
import { vi } from 'vitest';
import type { SimpleGit } from 'simple-git';

const mockGit: Partial<SimpleGit> = {
  log: vi.fn().mockResolvedValue({
    all: [
      {
        hash: 'abc123',
        message: 'feat: add new feature',
        date: '2025-01-15',
        author_name: 'Test User',
      },
    ],
  }),
  branch: vi.fn().mockResolvedValue({
    current: 'feature/test',
    all: ['main', 'feature/test'],
  }),
};

vi.mock('simple-git', () => ({
  simpleGit: () => mockGit,
}));

Testing Best Practices

DO ✓

it('returns data when API call succeeds', async () => {
  // Test happy path
});

it('throws error when API call fails', async () => {
  // Test error handling
});
// Good ✓
it('strips markdown headers when format is plain')

// Bad ✗
it('works correctly')
// Mock file system, network, and external APIs
vi.mock('fs/promises');
vi.stubGlobal('fetch', mockFetch);
beforeEach(() => {
  vi.clearAllMocks();
});

DON’T ✗

  • Don’t make real network requests
  • Don’t depend on file system state
  • Don’t rely on specific git repository state
  • Don’t use real API keys or credentials
  • Don’t write tests that depend on execution order

Coverage Reports

Generate an HTML coverage report:
npm run test:coverage
open coverage/index.html

Coverage Configuration

Vitest is configured to generate coverage using v8:
// vitest.config.ts
export default defineConfig({
  test: {
    globals: true,
    environment: 'node',
    coverage: {
      provider: 'v8',
      reporter: ['text', 'json', 'html'],
      exclude: [
        'node_modules/**',
        'dist/**',
        '**/*.test.ts',
        '**/*.config.*',
      ],
    },
  },
});
When adding new features, aim to maintain or improve test coverage. Every new module in src/core/ should have a corresponding test file.

Quality Checklist

Before submitting a pull request, ensure all checks pass:
1

TypeScript compilation

npm run typecheck
Must complete with zero errors
2

Linting

npm run lint
Must complete with zero errors
3

Formatting

npm run format:check
All files must be properly formatted
4

Tests

npm test
All tests must pass
5

Production build

npm run build
Build must complete successfully
These same checks run automatically in CI on every pull request across Node.js 18, 20, and 22.

Manual Testing

For changes affecting CLI behavior, manual smoke testing is recommended:
npm run build
npm link

2. Test in a Real Repository

# Navigate to any git repository
cd /path/to/your/project

# Test affected commands
devdaily standup --days 1 --no-copy
devdaily pr --debug --no-copy
devdaily week --no-copy
devdaily doctor
Use --debug to see the full prompt and context sent to Copilot CLI. Use --no-copy to prevent clipboard writes during testing.

3. Cleanup

When done, unlink the global command:
npm unlink -g devdaily-ai

Debugging Tests

Run a Single Test File

npx vitest run tests/notifications.test.ts

Run Tests Matching a Pattern

npx vitest run -t "webhook"

Enable Verbose Output

npx vitest run --reporter=verbose

Clear Vitest Cache

If tests behave unexpectedly:
npx vitest run --clearCache

Continuous Integration

Tests run automatically on every pull request via GitHub Actions:
# .github/workflows/ci.yml
strategy:
  matrix:
    node-version: [18, 20, 22]
    os: [ubuntu-latest, macos-latest]

steps:
  - run: npm run typecheck
  - run: npm run lint
  - run: npm run format:check
  - run: npm test
  - run: npm run build
All checks must pass before a PR can be merged.

Next Steps

Setup Guide

Set up your development environment

Architecture

Understand the project structure

Contributing

Submit your first contribution