Skip to content
This repository was archived by the owner on Jun 26, 2026. It is now read-only.

Commit 509f702

Browse files
committed
docs: update TESTING.md with component vs. page test rules, Vitest/MSW migration
1 parent db487cd commit 509f702

1 file changed

Lines changed: 46 additions & 20 deletions

File tree

docs/TESTING.md

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,30 @@ Do NOT write all test cases first and then implement everything at once.
1515

1616
| Layer | Tool | Scope |
1717
|-------|------|-------|
18-
| Unit / Component | Jest + React Testing Library | Hooks, services, component rendering, form logic |
18+
| Unit / Component | Vitest + React Testing Library | Hooks, services, component rendering, form logic |
1919
| E2e / Feature validation | Cypress | Validate features.json entries in real browser |
2020
| API mocking | MSW (Mock Service Worker) | GitHub API + K8s API — mock everything first, real cluster later |
2121

2222
## Mock Strategy
2323

24-
Use MSW for all API mocking (GitHub + K8s). If SDK hooks require module-level mocks in unit tests (because they depend on Console runtime internals), fall back to Jest mocks — but try MSW first.
24+
MSW is the primary mocking strategy for anything that hits the network (GitHub API, K8s API, Go backend). K8s API mocking uses MSW WebSocket capability.
2525

26-
- **Start:** Mock everything via MSW (GitHub API, K8s API)
27-
- **Later:** Real cluster for e2e — SDK hooks work natively, GitHub API remains mocked
28-
- **Fallback:** If SDK hooks can't be driven by MSW alone in unit tests, use Jest module mocks
26+
`vi.mock` is only for framework and library internals that have no external service:
27+
- `react-i18next` (translation hook)
28+
- `@openshift-console/dynamic-plugin-sdk` (console shell runtime components like DocumentTitle, ListPageHeader, consoleFetchJSON)
29+
- `@patternfly/react-icons` (UI library)
30+
- `react-router-dom-v5-compat` (framework routing)
31+
- `libsodium-wrappers` (WASM crypto library)
32+
33+
If it makes an HTTP or WebSocket call, mock it with MSW, not `vi.mock`.
2934

3035
## File Conventions
3136

3237
| Type | Location |
3338
|------|----------|
34-
| Unit / Component tests | `src/**/*.test.ts\|tsx` |
39+
| Component tests | `src/pages/<name>/components/*.test.ts\|tsx`, `src/common/components/*.test.ts\|tsx` |
40+
| Page tests | `src/pages/<name>/*.test.ts\|tsx` |
41+
| Service / Hook / Util tests | `src/common/**/*.test.ts\|tsx` |
3542
| E2e specs | `e2e/<feature-name>/*.test.ts` |
3643
| MSW handlers | `testing/msw/handlers.ts` |
3744

@@ -42,9 +49,26 @@ Use MSW for all API mocking (GitHub + K8s). If SDK hooks require module-level mo
4249
| Service interfaces | Unit | `FunctionService.generateFunction()` returns expected files |
4350
| React hooks | Unit | `useFunctionService()` returns service instance |
4451
| Components | Component | `CreateForm` renders all fields, validates input |
45-
| Views | Component + E2e | `FunctionListPage` shows empty state, table |
52+
| Pages | Component + E2e | `FunctionsListPage` shows empty state, table |
4653
| User flows | E2e | Create form → submit → list shows new function |
4754

55+
## Component vs. Page Tests
56+
57+
Every component gets its own exhaustive test file. Every page gets its own test file that tests the page's orchestration and integration with its components.
58+
59+
**Component tests** cover:
60+
- Rendering based on props (all states and variants)
61+
- User interactions that trigger callbacks (clicks, input, form validation)
62+
- Internal state (expand/collapse, selection)
63+
64+
**Page tests** cover:
65+
- Component is present on the page and wired correctly
66+
- Data flows from hooks/services to components (correct props)
67+
- User actions that trigger cross-component effects or service calls (e.g., form submit calls service, then navigates)
68+
- Page-level states: loading, error, empty
69+
70+
Overlap between component tests and page tests is expected and acceptable. They test at different levels: component tests verify the component works in isolation, page tests verify the page's orchestration logic works correctly.
71+
4872
## Testing Best Practices
4973

5074
1. **User-Centric Testing** — Test what users see and interact with.
@@ -63,48 +87,50 @@ Use MSW for all API mocking (GitHub + K8s). If SDK hooks require module-level mo
6387

6488
## Mocking Patterns
6589

90+
MSW is the primary approach. `vi.mock` is rare (see Mock Strategy above).
91+
6692
Use ESM `import` at top of file. Never use `require('react')` or `React.createElement()` in mocks.
67-
Prefer `jest.mock()` for modules, `jest.fn()` for components. Keep mocks simple.
93+
Keep mocks simple.
6894

69-
**Correct patterns:**
95+
**Correct patterns (for the rare `vi.mock` cases):**
7096

7197
```typescript
7298
// Return null
73-
jest.mock('../MyComponent', () => () => null);
99+
vi.mock('../MyComponent', () => () => null);
74100

75101
// Return string
76-
jest.mock('../LoadingSpinner', () => () => 'Loading...');
102+
vi.mock('../LoadingSpinner', () => () => 'Loading...');
77103

78104
// Return children directly
79-
jest.mock('../Wrapper', () => ({ children }) => children);
105+
vi.mock('../Wrapper', () => ({ children }) => children);
80106

81-
// Track calls with jest.fn
82-
jest.mock('../ButtonBar', () => jest.fn(({ children }) => children));
107+
// Track calls with vi.fn
108+
vi.mock('../ButtonBar', () => vi.fn(({ children }) => children));
83109

84-
// Mock custom hooks
85-
jest.mock('../useCustomHook', () => ({
86-
useCustomHook: jest.fn(() => [/* mock data */]),
110+
// Mock framework hooks
111+
vi.mock('react-i18next', () => ({
112+
useTranslation: () => ({ t: (key: string) => key }),
87113
}));
88114
```
89115

90116
**Forbidden patterns:**
91117

92118
```typescript
93119
// NEVER - require() in mocks
94-
jest.mock('../Component', () => {
120+
vi.mock('../Component', () => {
95121
const React = require('react');
96122
return () => React.createElement('div');
97123
});
98124

99125
// NEVER - JSX in mocks
100-
jest.mock('../Component', () => () => <div>Mock</div>);
126+
vi.mock('../Component', () => () => <div>Mock</div>);
101127
```
102128

103129
**Clean up mocks:**
104130

105131
```typescript
106132
afterEach(() => {
107-
jest.restoreAllMocks();
133+
vi.restoreAllMocks();
108134
});
109135
```
110136

0 commit comments

Comments
 (0)