balda-js 0.0.1
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/.github/workflows/publish.yml +38 -0
- package/.husky/pre-commit +19 -0
- package/.nvmrc +1 -0
- package/LICENSE +21 -0
- package/README.md +46 -0
- package/deno.lock +2454 -0
- package/docs/README.md +135 -0
- package/docs/blog/authors.yml +6 -0
- package/docs/blog/tags.yml +4 -0
- package/docs/cli.md +109 -0
- package/docs/docs/core-concepts/controllers.md +393 -0
- package/docs/docs/core-concepts/middleware.md +302 -0
- package/docs/docs/core-concepts/request-response.md +486 -0
- package/docs/docs/core-concepts/routing.md +388 -0
- package/docs/docs/core-concepts/server.md +332 -0
- package/docs/docs/cron/overview.md +70 -0
- package/docs/docs/examples/rest-api.md +595 -0
- package/docs/docs/getting-started/configuration.md +168 -0
- package/docs/docs/getting-started/installation.md +125 -0
- package/docs/docs/getting-started/quick-start.md +273 -0
- package/docs/docs/intro.md +46 -0
- package/docs/docs/plugins/cookie.md +424 -0
- package/docs/docs/plugins/cors.md +295 -0
- package/docs/docs/plugins/file.md +382 -0
- package/docs/docs/plugins/helmet.md +388 -0
- package/docs/docs/plugins/json.md +338 -0
- package/docs/docs/plugins/log.md +592 -0
- package/docs/docs/plugins/overview.md +390 -0
- package/docs/docs/plugins/rate-limiter.md +347 -0
- package/docs/docs/plugins/static.md +352 -0
- package/docs/docs/plugins/swagger.md +411 -0
- package/docs/docs/plugins/urlencoded.md +76 -0
- package/docs/docs/testing/examples.md +384 -0
- package/docs/docs/testing/mock-server.md +311 -0
- package/docs/docs/testing/overview.md +76 -0
- package/docs/docusaurus.config.ts +144 -0
- package/docs/intro.md +78 -0
- package/docs/package.json +46 -0
- package/docs/sidebars.ts +72 -0
- package/docs/static/.nojekyll +0 -0
- package/docs/static/img/docusaurus-social-card.jpg +0 -0
- package/docs/static/img/docusaurus.png +0 -0
- package/docs/static/img/favicon.ico +0 -0
- package/docs/static/img/logo.svg +1 -0
- package/docs/static/img/undraw_docusaurus_mountain.svg +37 -0
- package/docs/static/img/undraw_docusaurus_react.svg +170 -0
- package/docs/static/img/undraw_docusaurus_tree.svg +40 -0
- package/docs/tsconfig.json +8 -0
- package/package.json +91 -0
- package/speed_test.sh +3 -0
- package/test/benchmark/index.ts +17 -0
- package/test/cli/cli.ts +7 -0
- package/test/commands/test.ts +42 -0
- package/test/controllers/file_upload.ts +29 -0
- package/test/controllers/urlencoded.ts +13 -0
- package/test/controllers/users.ts +111 -0
- package/test/cron/index.ts +6 -0
- package/test/cron/test_cron.ts +8 -0
- package/test/cron/test_cron_imported.ts +8 -0
- package/test/native_env.ts +16 -0
- package/test/resources/test.txt +1 -0
- package/test/server/index.ts +3 -0
- package/test/server/instance.ts +63 -0
- package/test/suite/upload.test.ts +23 -0
- package/test/suite/urlencoded.test.ts +23 -0
- package/test/suite/users.test.ts +76 -0
- package/todo.md +9 -0
- package/tsconfig.json +24 -0
- package/vitest.config.ts +17 -0
@@ -0,0 +1,384 @@
|
|
1
|
+
# Testing Examples
|
2
|
+
|
3
|
+
This guide provides practical examples of testing with Balda.js MockServer, covering common scenarios and patterns.
|
4
|
+
|
5
|
+
## Basic API Testing
|
6
|
+
|
7
|
+
### Simple GET Request
|
8
|
+
```typescript
|
9
|
+
import { describe, it, expect } from "vitest";
|
10
|
+
import { mockServer } from "test/server/instance";
|
11
|
+
|
12
|
+
describe("Basic API Tests", () => {
|
13
|
+
it("GET /users returns user list", async () => {
|
14
|
+
const res = await mockServer.get("/users");
|
15
|
+
|
16
|
+
expect(res.statusCode()).toBe(200);
|
17
|
+
expect(Array.isArray(res.body())).toBe(true);
|
18
|
+
expect(res.body().length).toBeGreaterThan(0);
|
19
|
+
});
|
20
|
+
});
|
21
|
+
```
|
22
|
+
|
23
|
+
### GET with Query Parameters
|
24
|
+
```typescript
|
25
|
+
it("GET /users with pagination", async () => {
|
26
|
+
const res = await mockServer.get("/users", {
|
27
|
+
query: { page: "1", limit: "5" }
|
28
|
+
});
|
29
|
+
|
30
|
+
expect(res.statusCode()).toBe(200);
|
31
|
+
expect(res.body().length).toBeLessThanOrEqual(5);
|
32
|
+
});
|
33
|
+
```
|
34
|
+
|
35
|
+
### GET with Headers
|
36
|
+
```typescript
|
37
|
+
it("GET /protected with authorization", async () => {
|
38
|
+
const res = await mockServer.get("/protected", {
|
39
|
+
headers: { "Authorization": "Bearer token123" }
|
40
|
+
});
|
41
|
+
|
42
|
+
expect(res.statusCode()).toBe(200);
|
43
|
+
});
|
44
|
+
```
|
45
|
+
|
46
|
+
## POST Request Testing
|
47
|
+
|
48
|
+
### JSON Body
|
49
|
+
```typescript
|
50
|
+
it("POST /users creates new user", async () => {
|
51
|
+
const newUser = {
|
52
|
+
name: "John Doe",
|
53
|
+
email: "john@example.com",
|
54
|
+
age: 30
|
55
|
+
};
|
56
|
+
|
57
|
+
const res = await mockServer.post("/users", { body: newUser });
|
58
|
+
|
59
|
+
expect(res.statusCode()).toBe(201);
|
60
|
+
expect(res.assertBodyDeepEqual(newUser));
|
61
|
+
});
|
62
|
+
```
|
63
|
+
|
64
|
+
### Form Data (URL Encoded)
|
65
|
+
```typescript
|
66
|
+
it("POST /users with form data", async () => {
|
67
|
+
const res = await mockServer.post("/users", {
|
68
|
+
urlencoded: {
|
69
|
+
name: "Jane Doe",
|
70
|
+
email: "jane@example.com",
|
71
|
+
age: "25"
|
72
|
+
}
|
73
|
+
});
|
74
|
+
|
75
|
+
expect(res.statusCode()).toBe(201);
|
76
|
+
expect(res.assertBodySubset({ name: "Jane Doe" }));
|
77
|
+
});
|
78
|
+
```
|
79
|
+
|
80
|
+
### File Upload
|
81
|
+
```typescript
|
82
|
+
it("POST /upload handles file upload", async () => {
|
83
|
+
const formData = new FormData();
|
84
|
+
const fileContent = new Uint8Array([1, 2, 3, 4, 5]);
|
85
|
+
formData.append("file", new Blob([fileContent]), "test.txt");
|
86
|
+
|
87
|
+
const res = await mockServer.post("/upload", { formData });
|
88
|
+
|
89
|
+
expect(res.statusCode()).toBe(200);
|
90
|
+
expect(res.body()).toEqual({
|
91
|
+
originalName: "test.txt",
|
92
|
+
filename: "file",
|
93
|
+
size: 5,
|
94
|
+
mimetype: "application/octet-stream"
|
95
|
+
});
|
96
|
+
});
|
97
|
+
```
|
98
|
+
|
99
|
+
## Error Handling Examples
|
100
|
+
|
101
|
+
### 404 Not Found
|
102
|
+
```typescript
|
103
|
+
it("GET /users/999 returns 404", async () => {
|
104
|
+
const res = await mockServer.get("/users/999");
|
105
|
+
|
106
|
+
expect(res.statusCode()).toBe(404);
|
107
|
+
expect(res.assertBodyDeepEqual({ error: "User not found" }));
|
108
|
+
});
|
109
|
+
```
|
110
|
+
|
111
|
+
### 409 Conflict
|
112
|
+
```typescript
|
113
|
+
it("POST /users returns 409 for existing user", async () => {
|
114
|
+
const existingUser = {
|
115
|
+
id: 1,
|
116
|
+
name: "John Doe",
|
117
|
+
email: "john.doe@example.com",
|
118
|
+
age: 20
|
119
|
+
};
|
120
|
+
|
121
|
+
const res = await mockServer.post("/users", { body: existingUser });
|
122
|
+
|
123
|
+
expect(res.statusCode()).toBe(409);
|
124
|
+
expect(res.assertBodyDeepEqual({ error: "User already exists" }));
|
125
|
+
});
|
126
|
+
```
|
127
|
+
|
128
|
+
### Server Error
|
129
|
+
```typescript
|
130
|
+
it("GET /users with shouldFail=true returns 500", async () => {
|
131
|
+
const res = await mockServer.get("/users", {
|
132
|
+
query: { shouldFail: "true" }
|
133
|
+
});
|
134
|
+
|
135
|
+
expect(res.statusCode()).toBe(500);
|
136
|
+
});
|
137
|
+
```
|
138
|
+
|
139
|
+
## CRUD Operations Testing
|
140
|
+
|
141
|
+
### Complete CRUD Test Suite
|
142
|
+
```typescript
|
143
|
+
describe("User CRUD Operations", () => {
|
144
|
+
it("creates a new user", async () => {
|
145
|
+
const newUser = { id: 3, name: "New User", email: "new@example.com", age: 30 };
|
146
|
+
|
147
|
+
const res = await mockServer.post("/users", { body: newUser });
|
148
|
+
|
149
|
+
expect(res.statusCode()).toBe(201);
|
150
|
+
expect(res.assertBodyDeepEqual(newUser));
|
151
|
+
});
|
152
|
+
|
153
|
+
it("reads a specific user", async () => {
|
154
|
+
const res = await mockServer.get("/users/1");
|
155
|
+
|
156
|
+
expect(res.statusCode()).toBe(200);
|
157
|
+
expect(res.assertBodySubset({ id: 1 }));
|
158
|
+
});
|
159
|
+
|
160
|
+
it("updates a user", async () => {
|
161
|
+
const res = await mockServer.patch("/users/1", {
|
162
|
+
body: { name: "Updated Name" }
|
163
|
+
});
|
164
|
+
|
165
|
+
expect(res.statusCode()).toBe(200);
|
166
|
+
expect(res.assertBodySubset({ id: 1, name: "Updated Name" }));
|
167
|
+
});
|
168
|
+
|
169
|
+
it("deletes a user", async () => {
|
170
|
+
const res = await mockServer.delete("/users/1");
|
171
|
+
|
172
|
+
expect(res.statusCode()).toBe(204);
|
173
|
+
});
|
174
|
+
});
|
175
|
+
```
|
176
|
+
|
177
|
+
## Authentication Testing
|
178
|
+
|
179
|
+
### Protected Routes
|
180
|
+
```typescript
|
181
|
+
describe("Authentication", () => {
|
182
|
+
it("accesses protected route with valid token", async () => {
|
183
|
+
const res = await mockServer.get("/protected", {
|
184
|
+
headers: { "Authorization": "Bearer valid-token" }
|
185
|
+
});
|
186
|
+
|
187
|
+
expect(res.statusCode()).toBe(200);
|
188
|
+
});
|
189
|
+
|
190
|
+
it("rejects access without token", async () => {
|
191
|
+
const res = await mockServer.get("/protected");
|
192
|
+
|
193
|
+
expect(res.statusCode()).toBe(401);
|
194
|
+
});
|
195
|
+
|
196
|
+
it("rejects access with invalid token", async () => {
|
197
|
+
const res = await mockServer.get("/protected", {
|
198
|
+
headers: { "Authorization": "Bearer invalid-token" }
|
199
|
+
});
|
200
|
+
|
201
|
+
expect(res.statusCode()).toBe(401);
|
202
|
+
});
|
203
|
+
});
|
204
|
+
```
|
205
|
+
|
206
|
+
## Cookie Testing
|
207
|
+
|
208
|
+
### Setting and Reading Cookies
|
209
|
+
```typescript
|
210
|
+
describe("Cookie Handling", () => {
|
211
|
+
it("sets session cookie on login", async () => {
|
212
|
+
const res = await mockServer.post("/login", {
|
213
|
+
body: { username: "user", password: "pass" }
|
214
|
+
});
|
215
|
+
|
216
|
+
expect(res.statusCode()).toBe(200);
|
217
|
+
expect(res.headers()["set-cookie"]).toContain("sessionId");
|
218
|
+
});
|
219
|
+
|
220
|
+
it("uses existing session cookie", async () => {
|
221
|
+
const res = await mockServer.get("/profile", {
|
222
|
+
cookies: { sessionId: "valid-session" }
|
223
|
+
});
|
224
|
+
|
225
|
+
expect(res.statusCode()).toBe(200);
|
226
|
+
});
|
227
|
+
});
|
228
|
+
```
|
229
|
+
|
230
|
+
## Advanced Testing Patterns
|
231
|
+
|
232
|
+
### Testing with Custom Headers
|
233
|
+
```typescript
|
234
|
+
it("handles custom headers", async () => {
|
235
|
+
const res = await mockServer.get("/api/data", {
|
236
|
+
headers: {
|
237
|
+
"X-API-Key": "secret-key",
|
238
|
+
"X-Client-Version": "1.0.0",
|
239
|
+
"Content-Type": "application/json"
|
240
|
+
}
|
241
|
+
});
|
242
|
+
|
243
|
+
expect(res.statusCode()).toBe(200);
|
244
|
+
});
|
245
|
+
```
|
246
|
+
|
247
|
+
### Testing with IP Address
|
248
|
+
```typescript
|
249
|
+
it("handles IP-based logic", async () => {
|
250
|
+
const res = await mockServer.get("/location", {
|
251
|
+
ip: "192.168.1.100"
|
252
|
+
});
|
253
|
+
|
254
|
+
expect(res.statusCode()).toBe(200);
|
255
|
+
expect(res.body()).toHaveProperty("ip", "192.168.1.100");
|
256
|
+
});
|
257
|
+
```
|
258
|
+
|
259
|
+
### Testing Query Parameters
|
260
|
+
```typescript
|
261
|
+
it("handles complex query parameters", async () => {
|
262
|
+
const res = await mockServer.get("/search", {
|
263
|
+
query: {
|
264
|
+
q: "search term",
|
265
|
+
category: "technology",
|
266
|
+
sort: "date",
|
267
|
+
order: "desc",
|
268
|
+
page: "1",
|
269
|
+
limit: "20"
|
270
|
+
}
|
271
|
+
});
|
272
|
+
|
273
|
+
expect(res.statusCode()).toBe(200);
|
274
|
+
expect(res.body()).toHaveProperty("results");
|
275
|
+
});
|
276
|
+
```
|
277
|
+
|
278
|
+
## Testing Middleware
|
279
|
+
|
280
|
+
### Rate Limiting
|
281
|
+
```typescript
|
282
|
+
describe("Rate Limiting", () => {
|
283
|
+
it("allows requests within limit", async () => {
|
284
|
+
const res = await mockServer.get("/api/limited");
|
285
|
+
expect(res.statusCode()).toBe(200);
|
286
|
+
});
|
287
|
+
|
288
|
+
it("blocks requests over limit", async () => {
|
289
|
+
// Simulate multiple requests
|
290
|
+
for (let i = 0; i < 10; i++) {
|
291
|
+
await mockServer.get("/api/limited");
|
292
|
+
}
|
293
|
+
|
294
|
+
const res = await mockServer.get("/api/limited");
|
295
|
+
expect(res.statusCode()).toBe(429);
|
296
|
+
});
|
297
|
+
});
|
298
|
+
```
|
299
|
+
|
300
|
+
### CORS Testing
|
301
|
+
```typescript
|
302
|
+
describe("CORS", () => {
|
303
|
+
it("handles preflight OPTIONS request", async () => {
|
304
|
+
const res = await mockServer.request("OPTIONS", "/api/data", {
|
305
|
+
headers: {
|
306
|
+
"Origin": "https://example.com",
|
307
|
+
"Access-Control-Request-Method": "POST"
|
308
|
+
}
|
309
|
+
});
|
310
|
+
|
311
|
+
expect(res.statusCode()).toBe(200);
|
312
|
+
expect(res.headers()["access-control-allow-origin"]).toBe("*");
|
313
|
+
});
|
314
|
+
});
|
315
|
+
```
|
316
|
+
|
317
|
+
## Performance Testing
|
318
|
+
|
319
|
+
### Load Testing with MockServer
|
320
|
+
```typescript
|
321
|
+
describe("Performance", () => {
|
322
|
+
it("handles multiple concurrent requests", async () => {
|
323
|
+
const promises = Array.from({ length: 100 }, () =>
|
324
|
+
mockServer.get("/api/data")
|
325
|
+
);
|
326
|
+
|
327
|
+
const results = await Promise.all(promises);
|
328
|
+
|
329
|
+
results.forEach(res => {
|
330
|
+
expect(res.statusCode()).toBe(200);
|
331
|
+
});
|
332
|
+
});
|
333
|
+
});
|
334
|
+
```
|
335
|
+
|
336
|
+
## Best Practices
|
337
|
+
|
338
|
+
### Test Organization
|
339
|
+
```typescript
|
340
|
+
describe("User API", () => {
|
341
|
+
describe("GET /users", () => {
|
342
|
+
it("returns all users", async () => {
|
343
|
+
// Test implementation
|
344
|
+
});
|
345
|
+
|
346
|
+
it("supports pagination", async () => {
|
347
|
+
// Test implementation
|
348
|
+
});
|
349
|
+
});
|
350
|
+
|
351
|
+
describe("POST /users", () => {
|
352
|
+
it("creates new user", async () => {
|
353
|
+
// Test implementation
|
354
|
+
});
|
355
|
+
|
356
|
+
it("validates required fields", async () => {
|
357
|
+
// Test implementation
|
358
|
+
});
|
359
|
+
});
|
360
|
+
});
|
361
|
+
```
|
362
|
+
|
363
|
+
### Type Safety
|
364
|
+
```typescript
|
365
|
+
interface User {
|
366
|
+
id: number;
|
367
|
+
name: string;
|
368
|
+
email: string;
|
369
|
+
age: number;
|
370
|
+
}
|
371
|
+
|
372
|
+
describe("Type-safe testing", () => {
|
373
|
+
it("returns properly typed user data", async () => {
|
374
|
+
const res = await mockServer.get<User[]>("/users");
|
375
|
+
const users = res.body() as User[];
|
376
|
+
|
377
|
+
expect(users[0]).toHaveProperty("id");
|
378
|
+
expect(users[0]).toHaveProperty("name");
|
379
|
+
expect(users[0]).toHaveProperty("email");
|
380
|
+
});
|
381
|
+
});
|
382
|
+
```
|
383
|
+
|
384
|
+
These examples demonstrate the flexibility and power of MockServer for comprehensive API testing in Balda.js applications.
|
@@ -0,0 +1,311 @@
|
|
1
|
+
# MockServer Testing
|
2
|
+
|
3
|
+
The MockServer is Balda.js's powerful testing utility that allows you to test HTTP endpoints without starting a real server. This enables fast, isolated testing of your controllers and middleware.
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
MockServer simulates HTTP requests by:
|
8
|
+
- Executing the complete middleware chain
|
9
|
+
- Running route handlers with mocked request/response objects
|
10
|
+
- Providing realistic testing environment without network overhead
|
11
|
+
- Supporting all HTTP methods and content types
|
12
|
+
|
13
|
+
## Basic Usage
|
14
|
+
|
15
|
+
```typescript
|
16
|
+
import { describe, it, expect } from "vitest";
|
17
|
+
import { mockServer } from "test/server/instance";
|
18
|
+
|
19
|
+
describe("API Tests", () => {
|
20
|
+
it("GET /users returns all users", async () => {
|
21
|
+
const res = await mockServer.get("/users");
|
22
|
+
expect(res.statusCode()).toBe(200);
|
23
|
+
expect(Array.isArray(res.body())).toBe(true);
|
24
|
+
});
|
25
|
+
});
|
26
|
+
```
|
27
|
+
|
28
|
+
## HTTP Methods
|
29
|
+
|
30
|
+
MockServer provides methods for all HTTP verbs:
|
31
|
+
|
32
|
+
### GET Requests
|
33
|
+
```typescript
|
34
|
+
// Simple GET request
|
35
|
+
const res = await mockServer.get("/users");
|
36
|
+
|
37
|
+
// GET with query parameters
|
38
|
+
const res = await mockServer.get("/users", {
|
39
|
+
query: { page: "1", limit: "10" }
|
40
|
+
});
|
41
|
+
|
42
|
+
// GET with headers
|
43
|
+
const res = await mockServer.get("/users", {
|
44
|
+
headers: { "Authorization": "Bearer token123" }
|
45
|
+
});
|
46
|
+
```
|
47
|
+
|
48
|
+
### POST Requests
|
49
|
+
```typescript
|
50
|
+
// POST with JSON body
|
51
|
+
const res = await mockServer.post("/users", {
|
52
|
+
body: { name: "John", email: "john@example.com" }
|
53
|
+
});
|
54
|
+
|
55
|
+
// POST with form data
|
56
|
+
const res = await mockServer.post("/users", {
|
57
|
+
urlencoded: { name: "John", email: "john@example.com" }
|
58
|
+
});
|
59
|
+
|
60
|
+
// POST with file upload
|
61
|
+
const formData = new FormData();
|
62
|
+
formData.append("file", new Blob(["content"]), "test.txt");
|
63
|
+
const res = await mockServer.post("/upload", { formData });
|
64
|
+
```
|
65
|
+
|
66
|
+
### Other HTTP Methods
|
67
|
+
```typescript
|
68
|
+
// PUT request
|
69
|
+
const res = await mockServer.put("/users/1", {
|
70
|
+
body: { name: "Updated Name" }
|
71
|
+
});
|
72
|
+
|
73
|
+
// PATCH request
|
74
|
+
const res = await mockServer.patch("/users/1", {
|
75
|
+
body: { name: "Patched Name" }
|
76
|
+
});
|
77
|
+
|
78
|
+
// DELETE request
|
79
|
+
const res = await mockServer.delete("/users/1");
|
80
|
+
```
|
81
|
+
|
82
|
+
## Request Options
|
83
|
+
|
84
|
+
MockServer supports comprehensive request configuration:
|
85
|
+
|
86
|
+
```typescript
|
87
|
+
interface MockServerOptions {
|
88
|
+
headers?: Record<string, string>;
|
89
|
+
query?: Record<string, string>;
|
90
|
+
cookies?: Record<string, string>;
|
91
|
+
ip?: string;
|
92
|
+
body?: any;
|
93
|
+
formData?: FormData;
|
94
|
+
urlencoded?: Record<string, string>;
|
95
|
+
}
|
96
|
+
```
|
97
|
+
|
98
|
+
### Headers
|
99
|
+
```typescript
|
100
|
+
const res = await mockServer.get("/protected", {
|
101
|
+
headers: {
|
102
|
+
"Authorization": "Bearer token123",
|
103
|
+
"Content-Type": "application/json"
|
104
|
+
}
|
105
|
+
});
|
106
|
+
```
|
107
|
+
|
108
|
+
### Query Parameters
|
109
|
+
```typescript
|
110
|
+
const res = await mockServer.get("/search", {
|
111
|
+
query: {
|
112
|
+
q: "search term",
|
113
|
+
page: "1",
|
114
|
+
limit: "20"
|
115
|
+
}
|
116
|
+
});
|
117
|
+
```
|
118
|
+
|
119
|
+
### Cookies
|
120
|
+
```typescript
|
121
|
+
const res = await mockServer.get("/profile", {
|
122
|
+
cookies: {
|
123
|
+
sessionId: "abc123",
|
124
|
+
userId: "456"
|
125
|
+
}
|
126
|
+
});
|
127
|
+
```
|
128
|
+
|
129
|
+
### IP Address
|
130
|
+
```typescript
|
131
|
+
const res = await mockServer.get("/location", {
|
132
|
+
ip: "192.168.1.1"
|
133
|
+
});
|
134
|
+
```
|
135
|
+
|
136
|
+
## Response Assertions
|
137
|
+
|
138
|
+
MockResponse provides powerful assertion methods:
|
139
|
+
|
140
|
+
### Status Code Assertions
|
141
|
+
```typescript
|
142
|
+
const res = await mockServer.get("/users");
|
143
|
+
expect(res.statusCode()).toBe(200);
|
144
|
+
expect(res.assertStatus(200)); // Alternative syntax
|
145
|
+
```
|
146
|
+
|
147
|
+
### Body Assertions
|
148
|
+
```typescript
|
149
|
+
// Exact body match
|
150
|
+
expect(res.assertBodyDeepEqual({ id: 1, name: "John" }));
|
151
|
+
|
152
|
+
// Partial body match
|
153
|
+
expect(res.assertBodySubset({ name: "John" }));
|
154
|
+
|
155
|
+
// Get body content
|
156
|
+
const body = res.body();
|
157
|
+
expect(body.name).toBe("John");
|
158
|
+
```
|
159
|
+
|
160
|
+
### Complete Example
|
161
|
+
```typescript
|
162
|
+
describe("User API", () => {
|
163
|
+
it("creates a new user", async () => {
|
164
|
+
const newUser = {
|
165
|
+
name: "Jane Doe",
|
166
|
+
email: "jane@example.com",
|
167
|
+
age: 25
|
168
|
+
};
|
169
|
+
|
170
|
+
const res = await mockServer.post("/users", { body: newUser });
|
171
|
+
|
172
|
+
expect(res.assertStatus(201));
|
173
|
+
expect(res.assertBodyDeepEqual(newUser));
|
174
|
+
});
|
175
|
+
|
176
|
+
it("returns 404 for non-existent user", async () => {
|
177
|
+
const res = await mockServer.get("/users/999");
|
178
|
+
|
179
|
+
expect(res.assertStatus(404));
|
180
|
+
expect(res.assertBodyDeepEqual({ error: "User not found" }));
|
181
|
+
});
|
182
|
+
});
|
183
|
+
```
|
184
|
+
|
185
|
+
## File Upload Testing
|
186
|
+
|
187
|
+
MockServer supports file upload testing with FormData:
|
188
|
+
|
189
|
+
```typescript
|
190
|
+
describe("File Upload", () => {
|
191
|
+
it("uploads a file successfully", async () => {
|
192
|
+
const formData = new FormData();
|
193
|
+
const fileContent = new Uint8Array([1, 2, 3, 4, 5]);
|
194
|
+
formData.append("file", new Blob([fileContent]), "test.txt");
|
195
|
+
|
196
|
+
const res = await mockServer.post("/upload", { formData });
|
197
|
+
|
198
|
+
expect(res.assertStatus(200));
|
199
|
+
expect(res.body()).toEqual({
|
200
|
+
originalName: "test.txt",
|
201
|
+
filename: "file",
|
202
|
+
size: 5,
|
203
|
+
mimetype: "application/octet-stream"
|
204
|
+
});
|
205
|
+
});
|
206
|
+
});
|
207
|
+
```
|
208
|
+
|
209
|
+
## Error Handling
|
210
|
+
|
211
|
+
MockServer properly handles and reports errors:
|
212
|
+
|
213
|
+
```typescript
|
214
|
+
describe("Error Handling", () => {
|
215
|
+
it("handles server errors gracefully", async () => {
|
216
|
+
const res = await mockServer.get("/users", {
|
217
|
+
query: { shouldFail: "true" }
|
218
|
+
});
|
219
|
+
|
220
|
+
expect(res.assertStatus(500));
|
221
|
+
expect(res.body()).toHaveProperty("error");
|
222
|
+
});
|
223
|
+
});
|
224
|
+
```
|
225
|
+
|
226
|
+
## Best Practices
|
227
|
+
|
228
|
+
### 1. Test Isolation
|
229
|
+
```typescript
|
230
|
+
describe("User Management", () => {
|
231
|
+
// Each test should be independent
|
232
|
+
it("creates user", async () => {
|
233
|
+
// Test implementation
|
234
|
+
});
|
235
|
+
|
236
|
+
it("updates user", async () => {
|
237
|
+
// Test implementation - doesn't depend on previous test
|
238
|
+
});
|
239
|
+
});
|
240
|
+
```
|
241
|
+
|
242
|
+
### 2. Descriptive Test Names
|
243
|
+
```typescript
|
244
|
+
// Good
|
245
|
+
it("POST /users returns 409 when user already exists", async () => {
|
246
|
+
// Test implementation
|
247
|
+
});
|
248
|
+
|
249
|
+
// Avoid
|
250
|
+
it("test user creation", async () => {
|
251
|
+
// Test implementation
|
252
|
+
});
|
253
|
+
```
|
254
|
+
|
255
|
+
### 3. Use TypeScript
|
256
|
+
```typescript
|
257
|
+
interface UserResponse {
|
258
|
+
id: number;
|
259
|
+
name: string;
|
260
|
+
email: string;
|
261
|
+
}
|
262
|
+
|
263
|
+
const res = await mockServer.get<UserResponse[]>("/users");
|
264
|
+
const users = res.body() as UserResponse[];
|
265
|
+
```
|
266
|
+
|
267
|
+
### 4. Test Edge Cases
|
268
|
+
```typescript
|
269
|
+
describe("User Validation", () => {
|
270
|
+
it("rejects invalid email format", async () => {
|
271
|
+
const res = await mockServer.post("/users", {
|
272
|
+
body: { name: "John", email: "invalid-email" }
|
273
|
+
});
|
274
|
+
|
275
|
+
expect(res.assertStatus(400));
|
276
|
+
});
|
277
|
+
|
278
|
+
it("requires all mandatory fields", async () => {
|
279
|
+
const res = await mockServer.post("/users", {
|
280
|
+
body: { name: "John" } // Missing email
|
281
|
+
});
|
282
|
+
|
283
|
+
expect(res.assertStatus(400));
|
284
|
+
});
|
285
|
+
});
|
286
|
+
```
|
287
|
+
|
288
|
+
## Advanced Features
|
289
|
+
|
290
|
+
### Custom Request Configuration
|
291
|
+
```typescript
|
292
|
+
const res = await mockServer.request("GET", "/api/data", {
|
293
|
+
headers: {
|
294
|
+
"X-Custom-Header": "value",
|
295
|
+
"Authorization": "Bearer token"
|
296
|
+
},
|
297
|
+
query: { filter: "active" },
|
298
|
+
cookies: { sessionId: "abc123" },
|
299
|
+
ip: "192.168.1.100"
|
300
|
+
});
|
301
|
+
```
|
302
|
+
|
303
|
+
### Testing Middleware
|
304
|
+
MockServer executes the complete middleware chain, allowing you to test:
|
305
|
+
- Authentication middleware
|
306
|
+
- Rate limiting
|
307
|
+
- CORS handling
|
308
|
+
- Request logging
|
309
|
+
- Custom business logic middleware
|
310
|
+
|
311
|
+
This comprehensive testing approach ensures your entire request pipeline works correctly in isolation.
|