Skip to main content

Running Tests

Wellpipe uses Vitest for testing across all packages.

Quick Commands

# Run all tests
npm test

# Run tests in watch mode
npm run test:watch

# Run tests with coverage
npm run test:coverage

# Run specific test file
npm test -- src/client/sleep.test.ts

Test Structure

Tests are colocated with source files:

src/
├── client/
│ ├── sleep.ts
│ ├── sleep.test.ts # Tests for sleep.ts
│ ├── recovery.ts
│ └── recovery.test.ts # Tests for recovery.ts
└── tools/
├── index.ts
└── index.test.ts

Writing Tests

Basic Test

import { describe, it, expect } from 'vitest';
import { WhoopClient } from './client';

describe('WhoopClient', () => {
it('should fetch sleep data', async () => {
const client = new WhoopClient({ tokenProvider: mockTokenProvider });
const sleep = await client.sleep.getRecent(7);

expect(sleep.records).toBeDefined();
expect(sleep.records.length).toBeGreaterThan(0);
});
});

Mocking External APIs

Never hit real APIs in tests. Use mocks:

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

// Mock fetch globally
global.fetch = vi.fn();

describe('WhoopClient', () => {
beforeEach(() => {
vi.clearAllMocks();
});

it('should handle API errors', async () => {
vi.mocked(fetch).mockResolvedValueOnce({
ok: false,
status: 401,
json: async () => ({ error: 'Unauthorized' })
} as Response);

const client = new WhoopClient({ tokenProvider: mockTokenProvider });

await expect(client.sleep.getRecent(7)).rejects.toThrow('Unauthorized');
});
});

Mock Token Provider

import { TokenProvider } from '@wellpipe/core';

const mockTokenProvider: TokenProvider = {
getAccessToken: vi.fn().mockResolvedValue('mock-access-token'),
getRefreshToken: vi.fn().mockResolvedValue('mock-refresh-token'),
updateTokens: vi.fn().mockResolvedValue(undefined),
isExpired: vi.fn().mockResolvedValue(false),
};

Fixtures

Use fixtures for consistent test data:

// __fixtures__/sleep.ts
export const mockSleepResponse = {
records: [
{
id: 'sleep-123',
start: '2024-12-20T22:00:00.000Z',
end: '2024-12-21T06:00:00.000Z',
score: {
sleep_performance_percentage: 92,
}
}
],
next_token: null
};

// In test
import { mockSleepResponse } from './__fixtures__/sleep';

vi.mocked(fetch).mockResolvedValueOnce({
ok: true,
json: async () => mockSleepResponse
} as Response);

Test Coverage

Run coverage report:

npm run test:coverage

Coverage thresholds are configured in vitest.config.ts:

export default defineConfig({
test: {
coverage: {
provider: 'v8',
reporter: ['text', 'html'],
thresholds: {
lines: 80,
functions: 80,
branches: 70,
}
}
}
});

Integration Tests

For testing actual API integration (not in CI):

# Set environment variables first
export WHOOP_ACCESS_TOKEN=your_real_token

# Run integration tests
npm run test:integration

Integration tests are skipped in CI and require real credentials.

Debugging Tests

VS Code

Add this to .vscode/launch.json:

{
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Tests",
"program": "${workspaceFolder}/node_modules/vitest/vitest.mjs",
"args": ["run", "${relativeFile}"],
"console": "integratedTerminal"
}
]
}

Console Debugging

it('should handle response', async () => {
const result = await client.sleep.getRecent(7);
console.log('Result:', JSON.stringify(result, null, 2));
expect(result).toBeDefined();
});

CI Configuration

Tests run automatically on PR:

# .github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm test
- run: npm run typecheck