lambder 1.0.129 → 1.0.130
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +49 -0
- package/dist/LambderMSW.d.ts +104 -0
- package/dist/LambderMSW.js +203 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/docs/MSW_MOCKING.md +590 -0
- package/examples/msw-mocking-example.ts +241 -0
- package/package.json +9 -1
- package/src/LambderMSW.ts +330 -0
- package/src/index.ts +11 -0
- package/tests/msw-integration.test.ts +183 -0
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
# MSW Mocking for Lambder
|
|
2
|
+
|
|
3
|
+
This guide explains how to use Mock Service Worker (MSW) with Lambder to mock API endpoints in your tests and development environment.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
MSW is an optional peer dependency. Install it in your project:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install --save-dev msw
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Overview
|
|
14
|
+
|
|
15
|
+
Lambder provides type-safe mocking utilities that work seamlessly with MSW to mock your Lambder API endpoints. This is especially useful for:
|
|
16
|
+
|
|
17
|
+
- **Testing**: Write unit and integration tests without hitting real APIs
|
|
18
|
+
- **Development**: Develop frontend features before backend APIs are ready
|
|
19
|
+
- **Storybook**: Create isolated component stories with mocked data
|
|
20
|
+
- **Offline Development**: Work without a network connection
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
### 1. Define Your API Contract
|
|
25
|
+
|
|
26
|
+
First, ensure you have your API contract defined:
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
// api-contract.ts
|
|
30
|
+
import { ApiContract } from 'lambder';
|
|
31
|
+
|
|
32
|
+
export const myApiContract = {
|
|
33
|
+
'user.getProfile': {
|
|
34
|
+
input: { userId: '' as string },
|
|
35
|
+
output: { name: '' as string, email: '' as string }
|
|
36
|
+
},
|
|
37
|
+
'user.updateProfile': {
|
|
38
|
+
input: { userId: '' as string, name: '' as string },
|
|
39
|
+
output: { success: true as boolean }
|
|
40
|
+
},
|
|
41
|
+
'admin.deleteUser': {
|
|
42
|
+
input: { userId: '' as string },
|
|
43
|
+
output: { success: true as boolean }
|
|
44
|
+
}
|
|
45
|
+
} satisfies ApiContract;
|
|
46
|
+
|
|
47
|
+
export type MyApiContract = typeof myApiContract;
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 2. Set Up MSW
|
|
51
|
+
|
|
52
|
+
Create mock handlers using Lambder's mocking utilities:
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
// mocks/handlers.ts
|
|
56
|
+
import { mockLambderApi } from 'lambder';
|
|
57
|
+
import type { MyApiContract } from './api-contract';
|
|
58
|
+
|
|
59
|
+
export const handlers = [
|
|
60
|
+
// Basic mock with static data
|
|
61
|
+
mockLambderApi<MyApiContract, 'user.getProfile'>(
|
|
62
|
+
'user.getProfile',
|
|
63
|
+
() => ({
|
|
64
|
+
name: 'John Doe',
|
|
65
|
+
email: 'john@example.com'
|
|
66
|
+
})
|
|
67
|
+
),
|
|
68
|
+
|
|
69
|
+
// Mock with dynamic response based on input
|
|
70
|
+
mockLambderApi<MyApiContract, 'user.updateProfile'>(
|
|
71
|
+
'user.updateProfile',
|
|
72
|
+
(input) => ({
|
|
73
|
+
success: true
|
|
74
|
+
}),
|
|
75
|
+
{
|
|
76
|
+
delay: 500 // Simulate network delay
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
];
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 3. Initialize MSW
|
|
83
|
+
|
|
84
|
+
#### For Node.js (Tests)
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// mocks/server.ts
|
|
88
|
+
import { setupServer } from 'msw/node';
|
|
89
|
+
import { handlers } from './handlers';
|
|
90
|
+
|
|
91
|
+
export const server = setupServer(...handlers);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
// vitest.setup.ts or jest.setup.ts
|
|
96
|
+
import { server } from './mocks/server';
|
|
97
|
+
|
|
98
|
+
beforeAll(() => server.listen());
|
|
99
|
+
afterEach(() => server.resetHandlers());
|
|
100
|
+
afterAll(() => server.close());
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
#### For Browser (Development/Storybook)
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
// mocks/browser.ts
|
|
107
|
+
import { setupWorker } from 'msw/browser';
|
|
108
|
+
import { handlers } from './handlers';
|
|
109
|
+
|
|
110
|
+
export const worker = setupWorker(...handlers);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// main.tsx or index.tsx
|
|
115
|
+
if (process.env.NODE_ENV === 'development') {
|
|
116
|
+
const { worker } = await import('./mocks/browser');
|
|
117
|
+
worker.start();
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## API Reference
|
|
122
|
+
|
|
123
|
+
### `mockLambderApi`
|
|
124
|
+
|
|
125
|
+
Creates a mock handler for a successful API response.
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
mockLambderApi<TContract, TApiName>(
|
|
129
|
+
apiName: TApiName,
|
|
130
|
+
responseFactory: (input) => output | Promise<output>,
|
|
131
|
+
options?: {
|
|
132
|
+
apiPath?: string; // Default: '/api'
|
|
133
|
+
delay?: number; // Delay in ms
|
|
134
|
+
apiVersion?: string; // Custom API version
|
|
135
|
+
}
|
|
136
|
+
)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Example:**
|
|
140
|
+
```typescript
|
|
141
|
+
mockLambderApi<MyApiContract, 'user.getProfile'>(
|
|
142
|
+
'user.getProfile',
|
|
143
|
+
(input) => {
|
|
144
|
+
// Access input with full type safety
|
|
145
|
+
console.log('Fetching profile for:', input.userId);
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
name: 'John Doe',
|
|
149
|
+
email: 'john@example.com'
|
|
150
|
+
};
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
delay: 300, // Simulate 300ms network delay
|
|
154
|
+
apiPath: '/api' // Custom API path
|
|
155
|
+
}
|
|
156
|
+
)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### `mockLambderApiError`
|
|
160
|
+
|
|
161
|
+
Creates a mock handler that returns an error response.
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
mockLambderApiError<TContract, TApiName>(
|
|
165
|
+
apiName: TApiName,
|
|
166
|
+
errorMessage: string,
|
|
167
|
+
options?: {
|
|
168
|
+
apiPath?: string;
|
|
169
|
+
delay?: number;
|
|
170
|
+
apiVersion?: string;
|
|
171
|
+
}
|
|
172
|
+
)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Example:**
|
|
176
|
+
```typescript
|
|
177
|
+
mockLambderApiError<MyApiContract, 'user.updateProfile'>(
|
|
178
|
+
'user.updateProfile',
|
|
179
|
+
'Failed to update profile',
|
|
180
|
+
{ delay: 500 }
|
|
181
|
+
)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### `mockLambderSessionExpired`
|
|
185
|
+
|
|
186
|
+
Simulates a session expired error.
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
mockLambderSessionExpired<TContract, TApiName>(
|
|
190
|
+
apiName: TApiName,
|
|
191
|
+
options?: {
|
|
192
|
+
apiPath?: string;
|
|
193
|
+
apiVersion?: string;
|
|
194
|
+
}
|
|
195
|
+
)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Example:**
|
|
199
|
+
```typescript
|
|
200
|
+
mockLambderSessionExpired<MyApiContract, 'user.updateProfile'>(
|
|
201
|
+
'user.updateProfile'
|
|
202
|
+
)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### `mockLambderNotAuthorized`
|
|
206
|
+
|
|
207
|
+
Simulates a "not authorized" error.
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
mockLambderNotAuthorized<TContract, TApiName>(
|
|
211
|
+
apiName: TApiName,
|
|
212
|
+
options?: {
|
|
213
|
+
apiPath?: string;
|
|
214
|
+
apiVersion?: string;
|
|
215
|
+
}
|
|
216
|
+
)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**Example:**
|
|
220
|
+
```typescript
|
|
221
|
+
mockLambderNotAuthorized<MyApiContract, 'admin.deleteUser'>(
|
|
222
|
+
'admin.deleteUser'
|
|
223
|
+
)
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### `mockLambderVersionExpired`
|
|
227
|
+
|
|
228
|
+
Simulates a version expired error.
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
mockLambderVersionExpired<TContract, TApiName>(
|
|
232
|
+
apiName: TApiName,
|
|
233
|
+
options?: {
|
|
234
|
+
apiPath?: string;
|
|
235
|
+
apiVersion?: string;
|
|
236
|
+
}
|
|
237
|
+
)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Example:**
|
|
241
|
+
```typescript
|
|
242
|
+
mockLambderVersionExpired<MyApiContract, 'user.getProfile'>(
|
|
243
|
+
'user.getProfile'
|
|
244
|
+
)
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### `createLambderApiHandler`
|
|
248
|
+
|
|
249
|
+
Helper function to group multiple handlers for the same API path.
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
createLambderApiHandler(
|
|
253
|
+
apiPath: string,
|
|
254
|
+
handlers: HttpHandler[]
|
|
255
|
+
)
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**Example:**
|
|
259
|
+
```typescript
|
|
260
|
+
import { setupServer } from 'msw/node';
|
|
261
|
+
import { createLambderApiHandler, mockLambderApi } from 'lambder';
|
|
262
|
+
|
|
263
|
+
const server = setupServer(
|
|
264
|
+
createLambderApiHandler('/api', [
|
|
265
|
+
mockLambderApi<MyApiContract, 'user.getProfile'>('user.getProfile', () => ({
|
|
266
|
+
name: 'Test User',
|
|
267
|
+
email: 'test@example.com'
|
|
268
|
+
})),
|
|
269
|
+
mockLambderApi<MyApiContract, 'user.updateProfile'>('user.updateProfile', (input) => ({
|
|
270
|
+
success: true
|
|
271
|
+
}))
|
|
272
|
+
])
|
|
273
|
+
);
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Testing Examples
|
|
277
|
+
|
|
278
|
+
### Vitest
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
// user-profile.test.ts
|
|
282
|
+
import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest';
|
|
283
|
+
import { server } from './mocks/server';
|
|
284
|
+
import { mockLambderApi } from 'lambder';
|
|
285
|
+
import type { MyApiContract } from './api-contract';
|
|
286
|
+
|
|
287
|
+
describe('User Profile', () => {
|
|
288
|
+
beforeAll(() => server.listen());
|
|
289
|
+
afterEach(() => server.resetHandlers());
|
|
290
|
+
afterAll(() => server.close());
|
|
291
|
+
|
|
292
|
+
it('should fetch user profile', async () => {
|
|
293
|
+
// Test with mocked data
|
|
294
|
+
const result = await caller.api('user.getProfile', { userId: '123' });
|
|
295
|
+
|
|
296
|
+
expect(result).toEqual({
|
|
297
|
+
name: 'John Doe',
|
|
298
|
+
email: 'john@example.com'
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('should handle errors', async () => {
|
|
303
|
+
// Override handler for this test
|
|
304
|
+
server.use(
|
|
305
|
+
mockLambderApiError<MyApiContract, 'user.getProfile'>(
|
|
306
|
+
'user.getProfile',
|
|
307
|
+
'User not found'
|
|
308
|
+
)
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
const result = await caller.api('user.getProfile', { userId: '999' });
|
|
312
|
+
|
|
313
|
+
expect(result).toBeNull();
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('should handle session expiry', async () => {
|
|
317
|
+
server.use(
|
|
318
|
+
mockLambderSessionExpired<MyApiContract, 'user.updateProfile'>(
|
|
319
|
+
'user.updateProfile'
|
|
320
|
+
)
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
const result = await caller.api('user.updateProfile', {
|
|
324
|
+
userId: '123',
|
|
325
|
+
name: 'New Name'
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
expect(result).toBeNull();
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### React Testing Library
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
337
|
+
import userEvent from '@testing-library/user-event';
|
|
338
|
+
import { server } from './mocks/server';
|
|
339
|
+
import { mockLambderApi } from 'lambder';
|
|
340
|
+
import UserProfile from './UserProfile';
|
|
341
|
+
|
|
342
|
+
test('displays user profile', async () => {
|
|
343
|
+
render(<UserProfile userId="123" />);
|
|
344
|
+
|
|
345
|
+
await waitFor(() => {
|
|
346
|
+
expect(screen.getByText('John Doe')).toBeInTheDocument();
|
|
347
|
+
expect(screen.getByText('john@example.com')).toBeInTheDocument();
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
test('handles update', async () => {
|
|
352
|
+
const user = userEvent.setup();
|
|
353
|
+
render(<UserProfile userId="123" />);
|
|
354
|
+
|
|
355
|
+
const nameInput = screen.getByLabelText('Name');
|
|
356
|
+
await user.clear(nameInput);
|
|
357
|
+
await user.type(nameInput, 'Jane Doe');
|
|
358
|
+
|
|
359
|
+
await user.click(screen.getByText('Save'));
|
|
360
|
+
|
|
361
|
+
await waitFor(() => {
|
|
362
|
+
expect(screen.getByText('Profile updated successfully')).toBeInTheDocument();
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
## Advanced Usage
|
|
368
|
+
|
|
369
|
+
### Dynamic Responses Based on Input
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
mockLambderApi<MyApiContract, 'user.getProfile'>(
|
|
373
|
+
'user.getProfile',
|
|
374
|
+
(input) => {
|
|
375
|
+
// Return different data based on input
|
|
376
|
+
const profiles = {
|
|
377
|
+
'1': { name: 'John Doe', email: 'john@example.com' },
|
|
378
|
+
'2': { name: 'Jane Smith', email: 'jane@example.com' }
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
return profiles[input.userId] || { name: 'Unknown', email: '' };
|
|
382
|
+
}
|
|
383
|
+
)
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### Simulating Loading States
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
mockLambderApi<MyApiContract, 'user.getProfile'>(
|
|
390
|
+
'user.getProfile',
|
|
391
|
+
async (input) => {
|
|
392
|
+
// Simulate slow network
|
|
393
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
394
|
+
|
|
395
|
+
return {
|
|
396
|
+
name: 'John Doe',
|
|
397
|
+
email: 'john@example.com'
|
|
398
|
+
};
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
delay: 2000 // Alternative way to add delay
|
|
402
|
+
}
|
|
403
|
+
)
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Conditional Error Responses
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
mockLambderApi<MyApiContract, 'user.updateProfile'>(
|
|
410
|
+
'user.updateProfile',
|
|
411
|
+
(input) => {
|
|
412
|
+
// Validate input and throw if needed
|
|
413
|
+
if (!input.name || input.name.length < 2) {
|
|
414
|
+
throw new Error('Name must be at least 2 characters');
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return { success: true };
|
|
418
|
+
}
|
|
419
|
+
)
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Multiple API Paths
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
// For different environments or API versions
|
|
426
|
+
export const devHandlers = [
|
|
427
|
+
mockLambderApi<MyApiContract, 'user.getProfile'>(
|
|
428
|
+
'user.getProfile',
|
|
429
|
+
() => ({ name: 'Dev User', email: 'dev@example.com' }),
|
|
430
|
+
{ apiPath: '/dev/api' }
|
|
431
|
+
)
|
|
432
|
+
];
|
|
433
|
+
|
|
434
|
+
export const prodHandlers = [
|
|
435
|
+
mockLambderApi<MyApiContract, 'user.getProfile'>(
|
|
436
|
+
'user.getProfile',
|
|
437
|
+
() => ({ name: 'Prod User', email: 'prod@example.com' }),
|
|
438
|
+
{ apiPath: '/api' }
|
|
439
|
+
)
|
|
440
|
+
];
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
## Storybook Integration
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
// .storybook/preview.tsx
|
|
447
|
+
import { initialize, mswLoader } from 'msw-storybook-addon';
|
|
448
|
+
import { handlers } from '../mocks/handlers';
|
|
449
|
+
|
|
450
|
+
initialize();
|
|
451
|
+
|
|
452
|
+
export const loaders = [mswLoader];
|
|
453
|
+
|
|
454
|
+
export const parameters = {
|
|
455
|
+
msw: {
|
|
456
|
+
handlers
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
```typescript
|
|
462
|
+
// UserProfile.stories.tsx
|
|
463
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
464
|
+
import { mockLambderApi, mockLambderApiError } from 'lambder';
|
|
465
|
+
import type { MyApiContract } from './api-contract';
|
|
466
|
+
import UserProfile from './UserProfile';
|
|
467
|
+
|
|
468
|
+
const meta: Meta<typeof UserProfile> = {
|
|
469
|
+
component: UserProfile,
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
export default meta;
|
|
473
|
+
type Story = StoryObj<typeof UserProfile>;
|
|
474
|
+
|
|
475
|
+
export const Default: Story = {
|
|
476
|
+
args: {
|
|
477
|
+
userId: '123'
|
|
478
|
+
},
|
|
479
|
+
parameters: {
|
|
480
|
+
msw: {
|
|
481
|
+
handlers: [
|
|
482
|
+
mockLambderApi<MyApiContract, 'user.getProfile'>(
|
|
483
|
+
'user.getProfile',
|
|
484
|
+
() => ({
|
|
485
|
+
name: 'John Doe',
|
|
486
|
+
email: 'john@example.com'
|
|
487
|
+
})
|
|
488
|
+
)
|
|
489
|
+
]
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
export const LoadingState: Story = {
|
|
495
|
+
args: {
|
|
496
|
+
userId: '123'
|
|
497
|
+
},
|
|
498
|
+
parameters: {
|
|
499
|
+
msw: {
|
|
500
|
+
handlers: [
|
|
501
|
+
mockLambderApi<MyApiContract, 'user.getProfile'>(
|
|
502
|
+
'user.getProfile',
|
|
503
|
+
() => ({
|
|
504
|
+
name: 'John Doe',
|
|
505
|
+
email: 'john@example.com'
|
|
506
|
+
}),
|
|
507
|
+
{ delay: 3000 }
|
|
508
|
+
)
|
|
509
|
+
]
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
export const ErrorState: Story = {
|
|
515
|
+
args: {
|
|
516
|
+
userId: '123'
|
|
517
|
+
},
|
|
518
|
+
parameters: {
|
|
519
|
+
msw: {
|
|
520
|
+
handlers: [
|
|
521
|
+
mockLambderApiError<MyApiContract, 'user.getProfile'>(
|
|
522
|
+
'user.getProfile',
|
|
523
|
+
'Failed to load user profile'
|
|
524
|
+
)
|
|
525
|
+
]
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
## Best Practices
|
|
532
|
+
|
|
533
|
+
1. **Keep Handlers in a Separate Directory**: Organize your mocks in a `mocks/` folder for easy maintenance.
|
|
534
|
+
|
|
535
|
+
2. **Use Type Safety**: Always specify your API contract types to get full IntelliSense support.
|
|
536
|
+
|
|
537
|
+
3. **Reset Handlers Between Tests**: Use `server.resetHandlers()` in `afterEach` to prevent test pollution.
|
|
538
|
+
|
|
539
|
+
4. **Mock Realistic Data**: Use realistic data in your mocks to catch potential issues early.
|
|
540
|
+
|
|
541
|
+
5. **Test Error Cases**: Don't just test happy paths - use error mocks to test error handling.
|
|
542
|
+
|
|
543
|
+
6. **Use Delays Sparingly**: Only add delays when testing loading states to keep tests fast.
|
|
544
|
+
|
|
545
|
+
7. **Version Your Mocks**: If using API versions, include them in your mock setup.
|
|
546
|
+
|
|
547
|
+
## Troubleshooting
|
|
548
|
+
|
|
549
|
+
### MSW Not Working
|
|
550
|
+
|
|
551
|
+
If MSW handlers aren't being called:
|
|
552
|
+
|
|
553
|
+
1. Ensure MSW is properly initialized before your tests/app
|
|
554
|
+
2. Check that the API path matches your configuration
|
|
555
|
+
3. Verify the API name in the handler matches what you're calling
|
|
556
|
+
4. Check browser console for MSW logs
|
|
557
|
+
|
|
558
|
+
### TypeScript Errors
|
|
559
|
+
|
|
560
|
+
If you see TypeScript errors about missing MSW types:
|
|
561
|
+
|
|
562
|
+
```bash
|
|
563
|
+
npm install --save-dev @types/msw
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
Or use the built-in `HttpHandler` type from Lambder:
|
|
567
|
+
|
|
568
|
+
```typescript
|
|
569
|
+
import type { HttpHandler } from 'lambder';
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
### Handlers Not Matching
|
|
573
|
+
|
|
574
|
+
MSW handlers are checked in order. If a handler isn't matching:
|
|
575
|
+
|
|
576
|
+
1. Put more specific handlers before generic ones
|
|
577
|
+
2. Use `server.listHandlers()` to debug which handlers are registered
|
|
578
|
+
3. Add console.log statements in your response factories
|
|
579
|
+
|
|
580
|
+
## Resources
|
|
581
|
+
|
|
582
|
+
- [MSW Documentation](https://mswjs.io/)
|
|
583
|
+
- [MSW Storybook Addon](https://storybook.js.org/addons/msw-storybook-addon)
|
|
584
|
+
- [Lambder API Contract Documentation](./TYPE_SAFE_QUICK_START.md)
|
|
585
|
+
|
|
586
|
+
## Support
|
|
587
|
+
|
|
588
|
+
For issues or questions:
|
|
589
|
+
- Open an issue on [GitHub](https://github.com/nesovera/lambder)
|
|
590
|
+
- Check existing documentation in the `/docs` folder
|