MetaDebate / docs /testing.md
vajeeda's picture
base structure of the project formed
b6e19c7
|
Raw
History Blame Contribute Delete
5.02 kB
# Testing — Strategy, Patterns & CLI Commands
## Purpose
Read this file when writing tests or running them.
Follow this exactly for every feature implementation.
---
## Core Principle
**Write tests after every feature. Never at the end of the project.**
Every test failure must tell you:
- WHICH test failed (test name + file)
- WHY it failed (expected vs received)
- WHERE it failed (line number)
---
## Testing Stack
- **Unit + Integration:** Jest + ts-jest
- **API Routes:** Supertest
- **UI Components:** React Testing Library
- **DB (Supabase):** Mocked with jest.mock or Supabase local instance
---
## Folder Structure
tests/
├── unit/
│ ├── lib/
│ └── utils/
├── integration/
│ ├── api/
│ └── db/
├── components/
└── setup.ts
---
## Test File Naming Convention
[feature-name].test.ts ← unit test
[feature-name].integration.test.ts ← integration test
[component-name].test.tsx ← component test
---
## Standard Test Template
```typescript
// tests/unit/lib/fetchUser.test.ts
import { fetchUser } from '@/lib/supabase'
describe('fetchUser', () => {
it('returns user when valid userId is provided', async () => {
const result = await fetchUser('valid-uuid')
expect(result).not.toBeNull()
expect(result).toHaveProperty('id')
})
it('returns null when userId is null', async () => {
const result = await fetchUser(null as any)
expect(result).toBeNull()
})
it('returns null when userId is empty string', async () => {
const result = await fetchUser('')
expect(result).toBeNull()
})
it('handles DB error gracefully', async () => {
// mock supabase to throw
jest.spyOn(supabase, 'from').mockImplementationOnce(() => {
throw new Error('DB connection failed')
})
const result = await fetchUser('valid-uuid')
expect(result).toBeNull()
})
})
```
---
## Edge Cases to Test for Every Feature
| Scenario | What to Test |
|----------|-------------|
| Empty input | null, undefined, empty string |
| Auth state | unauthenticated user, expired token |
| DB response | empty array, null, malformed data |
| Network | timeout, connection failure |
| Duplicates | calling same function twice simultaneously |
| Boundary | max length strings, zero values, negative numbers |
---
## CLI Commands
### Run all tests
```bash
npm test 2>&1 | tee logs/test.log
```
### Run a specific test file
```bash
npm test -- tests/unit/lib/fetchUser.test.ts 2>&1 | tee logs/test.log
```
### Run tests in watch mode
```bash
npm test -- --watch
```
### Run tests with coverage
```bash
npm test -- --coverage 2>&1 | tee logs/test.log
```
### Run only failed tests
```bash
npm test -- --onlyFailures 2>&1 | tee logs/test.log
```
---
## Test Log Format
All test output pipes to `logs/test.log`.
When a test fails, the log will show:
FAIL tests/unit/lib/fetchUser.test.ts
● fetchUser › returns null when userId is null
expect(received).toBeNull()
Received: { id: 'abc', name: 'test' }
14 | it('returns null when userId is null', async () => {
15 | const result = await fetchUser(null as any)
> 16 | expect(result).toBeNull()
| ^
17 | })
at Object.<anonymous> (tests/unit/lib/fetchUser.test.ts:16:20)
---
## scripts/test.sh
```bash
#!/bin/bash
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] Starting test run..." >> logs/test.log
npm test 2>&1 | tee -a logs/test.log
EXIT_CODE=${PIPESTATUS[0]}
if [ $EXIT_CODE -ne 0 ]; then
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] TESTS FAILED — see above for details" >> logs/test.log
else
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] ALL TESTS PASSED" >> logs/test.log
fi
exit $EXIT_CODE
```
---
## Test Update Rules
- [ ] Write tests immediately after each feature is implemented
- [ ] Never delete existing tests — only add or update
- [ ] If a test is skipped, add a comment explaining why
- [ ] All tests must pass before marking a phase complete
- [ ] Run full test suite before every deployment
---
## Mocking Supabase
```typescript
// tests/setup.ts
jest.mock('@/lib/supabase', () => ({
supabase: {
from: jest.fn().mockReturnValue({
select: jest.fn().mockReturnValue({
eq: jest.fn().mockResolvedValue({ data: [], error: null })
}),
insert: jest.fn().mockResolvedValue({ data: null, error: null }),
update: jest.fn().mockResolvedValue({ data: null, error: null }),
delete: jest.fn().mockResolvedValue({ data: null, error: null })
}),
auth: {
getSession: jest.fn().mockResolvedValue({ data: { session: null }, error: null }),
refreshSession: jest.fn().mockResolvedValue({ data: null, error: null })
}
}
}))
```
---
## What NOT to Do
- Do not write tests after the entire project is done
- Do not mock everything — integration tests must hit real logic
- Do not skip edge case tests to save time
- Do not ignore a failing test — fix it before moving on
- Do not write tests that always pass regardless of logic