| name | tdd-testing |
| description | Guide for Test-Driven Development in the DEVS platform. Use this when asked to write tests, implement TDD, or add test coverage for lib/ and stores/ code. |
Test-Driven Development (TDD) for DEVS
TDD is mandatory for all code in src/lib/ and src/stores/. This ensures LLMs can safely enhance features without causing regressions.
TDD Workflow
- Red: Write a failing test that describes the expected behavior
- Green: Write the minimum code necessary to make the test pass
- Refactor: Improve the code while keeping tests green
- Verify: Run
npm run test:coverage before committing
Test File Structure
Tests mirror the source structure:
src/lib/orchestrator.ts → src/test/lib/orchestrator.test.ts
src/stores/agentStore.ts → src/test/stores/agentStore.test.ts
src/components/AgentCard.tsx → src/test/components/AgentCard.test.tsx
Coverage Requirements
| Category | Target | Priority |
|---|
src/lib/** | 60%+ | 🔴 Critical |
src/stores/** | 60%+ | 🔴 Critical |
src/components/** | 30%+ | 🟡 Medium |
src/pages/** | 20%+ | 🟢 Low |
Test Commands
npm run test:watch
npm run test:run
npm run test:coverage
npm run test:e2e
Unit Test Template
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { myFunction, MyClass } from '@/lib/my-module'
vi.mock('@/lib/db', () => ({
db: {
entities: {
toArray: vi.fn(),
add: vi.fn(),
update: vi.fn(),
delete: vi.fn(),
},
},
}))
describe('myFunction', () => {
beforeEach(() => {
vi.clearAllMocks()
})
afterEach(() => {
})
describe('when given valid input', () => {
it('should return expected result', () => {
const result = myFunction('valid input')
expect(result).toBe('expected output')
})
it('should handle edge cases', () => {
const result = ()
(result).()
})
})
(, {
(, {
( ( )).()
})
})
})
(, {
:
( {
instance = ()
})
(, {
(instance.).()
})
(, {
instance.()
(instance.).()
})
})
Store Test Template
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { useEntityStore } from '@/stores/entityStore'
import { db } from '@/lib/db'
vi.mock('@/lib/db', () => ({
db: {
entities: {
toArray: vi.fn(),
add: vi.fn(),
update: vi.fn(),
delete: vi.fn(),
get: vi.fn(),
},
},
}))
describe('entityStore', () => {
beforeEach(() => {
useEntityStore.setState({
entities: [],
isLoading: false,
error: null,
})
vi.clearAllMocks()
})
describe('loadEntities', () => {
it('should load entities from database', async () => {
const mockEntities = [
{ id: '1', name: 'Entity 1' },
{ id: , : },
]
vi.(db..).(mockEntities)
useEntityStore.().()
(useEntityStore.().).(mockEntities)
(useEntityStore.().).()
})
(, () => {
vi.(db..).( ())
useEntityStore.().()
(useEntityStore.().).()
(useEntityStore.().).()
})
})
(, {
(, () => {
vi.(db..).()
result = useEntityStore.().({
: ,
})
(result).()
(result.).()
(db..).(
expect.({
: ,
}),
)
})
})
(, {
(, {
useEntityStore.({
: [{ : , : }],
})
result = useEntityStore.().()
(result).({ : , : })
})
(, {
result = useEntityStore.().()
(result).()
})
})
})
Component Test Template
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import { describe, it, expect, vi } from 'vitest'
import { MyComponent } from '@/components/MyComponent'
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}))
describe('MyComponent', () => {
it('renders correctly with required props', () => {
render(<MyComponent title="Test Title" />)
expect(screen.getByText('Test Title')).toBeInTheDocument()
})
it('calls onAction when button is clicked', async () => {
const mockOnAction = vi.fn()
render(<MyComponent title="Test" onAction={mockOnAction} />)
fireEvent.click(screen.())
( {
(mockOnAction).()
})
})
(, {
()
(screen.()).()
})
(, {
(
)
(screen.()).()
})
})
Async Testing Patterns
import { describe, it, expect, vi } from 'vitest'
describe('async operations', () => {
it('should resolve with expected value', async () => {
const result = await asyncFunction()
expect(result).toBe('expected')
})
it('should reject with error', async () => {
await expect(asyncFunction('invalid')).rejects.toThrow('Error message')
})
it('should debounce calls', async () => {
vi.useFakeTimers()
const callback = vi.fn()
debouncedFunction(callback)
debouncedFunction(callback)
debouncedFunction(callback)
expect(callback).not.toHaveBeenCalled()
vi.advanceTimersByTime(500)
expect(callback).toHaveBeenCalledTimes(1)
vi.useRealTimers()
})
})
Mocking Patterns
vi.mock('@/lib/llm', () => ({
LLMService: {
chat: vi.fn().mockResolvedValue({ content: 'mocked response' }),
},
}))
const mockFn = vi.fn()
mockFn.mockReturnValue('value')
mockFn.mockResolvedValue('async value')
mockFn.mockImplementation((x) => x * 2)
const spy = vi.spyOn(object, 'method')
expect(spy).toHaveBeenCalledWith(expectedArgs)
vi.stubGlobal('crypto', {
randomUUID: () => 'test-uuid-123',
})
Best Practices
- One assertion per test when possible, or related assertions
- Descriptive test names:
it('should return null when user is not found')
- Arrange-Act-Assert pattern: Setup → Execute → Verify
- Isolate tests: Each test should be independent
- Test behavior, not implementation: Focus on what, not how
- Use meaningful test data: Avoid magic numbers/strings