File size: 5,017 Bytes
b6e19c7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# 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