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