Testing
Testing patterns and conventions for DorkOS
Testing
DorkOS uses Vitest for all testing, with React Testing Library for component tests.
Running Tests
pnpm testRuns all tests in watch mode.
pnpm test -- --runRuns all tests once without watch mode. Useful for CI.
pnpm vitest run apps/server/src/services/__tests__/transcript-reader.test.tsRuns a single test file directly.
Test File Structure
Tests live alongside source code in __tests__/ directories:
Component Tests
Component tests require the jsdom environment directive and a mock Transport.
Add the environment directive
Every component test file must start with the jsdom directive:
/**
* @vitest-environment jsdom
*/Set up the mock Transport
Use createMockTransport() from @dorkos/test-utils and wrap components in TransportProvider:
import { describe, it, expect, vi } from 'vitest';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import { TransportProvider } from '@/layers/shared/model';
import { createMockTransport } from '@dorkos/test-utils';
const mockTransport = createMockTransport();
function Wrapper({ children }: { children: React.ReactNode }) {
return (
<TransportProvider transport={mockTransport}>
{children}
</TransportProvider>
);
}Write your tests
describe('MyComponent', () => {
it('renders expected content', () => {
render(<MyComponent />, { wrapper: Wrapper });
expect(screen.getByText('Expected')).toBeInTheDocument();
});
});Service Tests
Server tests mock Node.js modules like fs/promises:
import { describe, it, expect, vi } from 'vitest';
vi.mock('fs/promises');
describe('TranscriptReader', () => {
it('returns session when found', async () => {
vi.mocked(readFile).mockResolvedValue(Buffer.from(mockJsonl));
const result = await transcriptReader.getSession('test-id');
expect(result).toEqual(expect.objectContaining({ id: 'test-id' }));
});
});Hook Tests
import { renderHook, waitFor } from '@testing-library/react';
describe('useCustomHook', () => {
it('returns expected state', async () => {
const { result } = renderHook(() => useCustomHook(), {
wrapper: Wrapper,
});
await waitFor(() => {
expect(result.current.data).toBeDefined();
});
});
});Key Conventions
Prop
Type
Mock Browser APIs
Components that use browser APIs like matchMedia need explicit mocks. Add these in beforeAll and clean up in afterEach.
beforeAll(() => {
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
})),
});
});Test Utilities
The @dorkos/test-utils package provides:
createMockTransport()— Creates a fully mocked Transport object with all 9 methods- Mock factories for sessions, messages, and other domain objects
Anti-Patterns
Avoid these common testing mistakes.
// NEVER test implementation details
expect(component.state.isOpen).toBe(true); // Wrong - test behavior instead
// NEVER use waitFor without an assertion
await waitFor(() => {}); // Wrong - always include an expect()
// NEVER leave console mocks without cleanup
vi.spyOn(console, 'error'); // Add mockRestore in afterEach
// NEVER use arbitrary timeouts
await new Promise((r) => setTimeout(r, 1000)); // Wrong - use waitFor