@engjts/nexus 0.1.7 → 0.1.9
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/dist/advanced/playground/generatePlaygroundHTML.d.ts.map +1 -1
- package/dist/advanced/playground/generatePlaygroundHTML.js +107 -0
- package/dist/advanced/playground/generatePlaygroundHTML.js.map +1 -1
- package/dist/advanced/playground/playground.d.ts +19 -0
- package/dist/advanced/playground/playground.d.ts.map +1 -1
- package/dist/advanced/playground/playground.js +70 -0
- package/dist/advanced/playground/playground.js.map +1 -1
- package/dist/advanced/playground/types.d.ts +20 -0
- package/dist/advanced/playground/types.d.ts.map +1 -1
- package/dist/core/application.d.ts +14 -0
- package/dist/core/application.d.ts.map +1 -1
- package/dist/core/application.js +173 -71
- package/dist/core/application.js.map +1 -1
- package/dist/core/context-pool.d.ts +2 -13
- package/dist/core/context-pool.d.ts.map +1 -1
- package/dist/core/context-pool.js +7 -45
- package/dist/core/context-pool.js.map +1 -1
- package/dist/core/context.d.ts +108 -5
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/context.js +449 -53
- package/dist/core/context.js.map +1 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +9 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/middleware.d.ts +6 -0
- package/dist/core/middleware.d.ts.map +1 -1
- package/dist/core/middleware.js +83 -84
- package/dist/core/middleware.js.map +1 -1
- package/dist/core/performance/fast-json.d.ts +149 -0
- package/dist/core/performance/fast-json.d.ts.map +1 -0
- package/dist/core/performance/fast-json.js +473 -0
- package/dist/core/performance/fast-json.js.map +1 -0
- package/dist/core/router/file-router.d.ts +20 -7
- package/dist/core/router/file-router.d.ts.map +1 -1
- package/dist/core/router/file-router.js +41 -13
- package/dist/core/router/file-router.js.map +1 -1
- package/dist/core/router/index.d.ts +6 -0
- package/dist/core/router/index.d.ts.map +1 -1
- package/dist/core/router/index.js +33 -6
- package/dist/core/router/index.js.map +1 -1
- package/dist/core/router/radix-tree.d.ts +4 -1
- package/dist/core/router/radix-tree.d.ts.map +1 -1
- package/dist/core/router/radix-tree.js +7 -3
- package/dist/core/router/radix-tree.js.map +1 -1
- package/dist/core/serializer.d.ts +251 -0
- package/dist/core/serializer.d.ts.map +1 -0
- package/dist/core/serializer.js +290 -0
- package/dist/core/serializer.js.map +1 -0
- package/dist/core/types.d.ts +39 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -2
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/documentation/01-getting-started.md +0 -240
- package/documentation/02-context.md +0 -335
- package/documentation/03-routing.md +0 -397
- package/documentation/04-middleware.md +0 -483
- package/documentation/05-validation.md +0 -514
- package/documentation/06-error-handling.md +0 -465
- package/documentation/07-performance.md +0 -364
- package/documentation/08-adapters.md +0 -470
- package/documentation/09-api-reference.md +0 -548
- package/documentation/10-examples.md +0 -582
- package/documentation/11-deployment.md +0 -477
- package/documentation/12-sentry.md +0 -620
- package/documentation/13-sentry-data-storage.md +0 -996
- package/documentation/14-sentry-data-reference.md +0 -457
- package/documentation/15-sentry-summary.md +0 -409
- package/documentation/16-alerts-system.md +0 -745
- package/documentation/17-alert-adapters.md +0 -696
- package/documentation/18-alerts-implementation-summary.md +0 -385
- package/documentation/19-class-based-routing.md +0 -840
- package/documentation/20-websocket-realtime.md +0 -813
- package/documentation/21-cache-system.md +0 -510
- package/documentation/22-job-queue.md +0 -772
- package/documentation/23-sentry-plugin.md +0 -551
- package/documentation/24-testing-utilities.md +0 -1287
- package/documentation/25-api-versioning.md +0 -533
- package/documentation/26-context-store.md +0 -607
- package/documentation/27-dependency-injection.md +0 -329
- package/documentation/28-lifecycle-hooks.md +0 -521
- package/documentation/29-package-structure.md +0 -196
- package/documentation/30-plugin-system.md +0 -414
- package/documentation/31-jwt-authentication.md +0 -597
- package/documentation/32-cli.md +0 -268
- package/documentation/ALERTS-COMPLETE-SUMMARY.md +0 -429
- package/documentation/ALERTS-INDEX.md +0 -330
- package/documentation/ALERTS-QUICK-REFERENCE.md +0 -286
- package/documentation/README.md +0 -178
- package/documentation/index.html +0 -34
- package/modern_framework_paper.md +0 -1870
- package/public/css/style.css +0 -87
- package/public/index.html +0 -34
- package/public/js/app.js +0 -27
- package/src/advanced/cache/InMemoryCacheStore.ts +0 -68
- package/src/advanced/cache/MultiTierCache.ts +0 -194
- package/src/advanced/cache/RedisCacheStore.ts +0 -341
- package/src/advanced/cache/index.ts +0 -5
- package/src/advanced/cache/types.ts +0 -40
- package/src/advanced/graphql/SimpleDataLoader.ts +0 -42
- package/src/advanced/graphql/index.ts +0 -22
- package/src/advanced/graphql/server.ts +0 -252
- package/src/advanced/graphql/types.ts +0 -42
- package/src/advanced/jobs/InMemoryQueueStore.ts +0 -68
- package/src/advanced/jobs/JobQueue.ts +0 -556
- package/src/advanced/jobs/RedisQueueStore.ts +0 -367
- package/src/advanced/jobs/index.ts +0 -5
- package/src/advanced/jobs/types.ts +0 -70
- package/src/advanced/observability/APMManager.ts +0 -163
- package/src/advanced/observability/AlertManager.ts +0 -109
- package/src/advanced/observability/MetricRegistry.ts +0 -151
- package/src/advanced/observability/ObservabilityCenter.ts +0 -304
- package/src/advanced/observability/StructuredLogger.ts +0 -154
- package/src/advanced/observability/TracingManager.ts +0 -117
- package/src/advanced/observability/adapters.ts +0 -304
- package/src/advanced/observability/createObservabilityMiddleware.ts +0 -63
- package/src/advanced/observability/index.ts +0 -11
- package/src/advanced/observability/types.ts +0 -174
- package/src/advanced/playground/extractPathParams.ts +0 -6
- package/src/advanced/playground/generateFieldExample.ts +0 -31
- package/src/advanced/playground/generatePlaygroundHTML.ts +0 -1849
- package/src/advanced/playground/generateSummary.ts +0 -19
- package/src/advanced/playground/getTagFromPath.ts +0 -9
- package/src/advanced/playground/index.ts +0 -8
- package/src/advanced/playground/playground.ts +0 -170
- package/src/advanced/playground/types.ts +0 -20
- package/src/advanced/playground/zodToExample.ts +0 -16
- package/src/advanced/playground/zodToParams.ts +0 -15
- package/src/advanced/postman/buildAuth.ts +0 -31
- package/src/advanced/postman/buildBody.ts +0 -15
- package/src/advanced/postman/buildQueryParams.ts +0 -27
- package/src/advanced/postman/buildRequestItem.ts +0 -36
- package/src/advanced/postman/buildResponses.ts +0 -11
- package/src/advanced/postman/buildUrl.ts +0 -33
- package/src/advanced/postman/capitalize.ts +0 -4
- package/src/advanced/postman/generateCollection.ts +0 -59
- package/src/advanced/postman/generateEnvironment.ts +0 -34
- package/src/advanced/postman/generateExampleFromZod.ts +0 -21
- package/src/advanced/postman/generateFieldExample.ts +0 -45
- package/src/advanced/postman/generateName.ts +0 -20
- package/src/advanced/postman/generateUUID.ts +0 -11
- package/src/advanced/postman/getTagFromPath.ts +0 -10
- package/src/advanced/postman/index.ts +0 -28
- package/src/advanced/postman/postman.ts +0 -156
- package/src/advanced/postman/slugify.ts +0 -7
- package/src/advanced/postman/types.ts +0 -140
- package/src/advanced/realtime/index.ts +0 -18
- package/src/advanced/realtime/websocket.ts +0 -231
- package/src/advanced/sentry/index.ts +0 -1236
- package/src/advanced/sentry/types.ts +0 -355
- package/src/advanced/static/generateDirectoryListing.ts +0 -47
- package/src/advanced/static/generateETag.ts +0 -7
- package/src/advanced/static/getMimeType.ts +0 -9
- package/src/advanced/static/index.ts +0 -32
- package/src/advanced/static/isSafePath.ts +0 -13
- package/src/advanced/static/publicDir.ts +0 -21
- package/src/advanced/static/serveStatic.ts +0 -225
- package/src/advanced/static/spa.ts +0 -24
- package/src/advanced/static/types.ts +0 -159
- package/src/advanced/swagger/SwaggerGenerator.ts +0 -66
- package/src/advanced/swagger/buildOperation.ts +0 -61
- package/src/advanced/swagger/buildParameters.ts +0 -61
- package/src/advanced/swagger/buildRequestBody.ts +0 -21
- package/src/advanced/swagger/buildResponses.ts +0 -54
- package/src/advanced/swagger/capitalize.ts +0 -5
- package/src/advanced/swagger/convertPath.ts +0 -9
- package/src/advanced/swagger/createSwagger.ts +0 -12
- package/src/advanced/swagger/generateOperationId.ts +0 -21
- package/src/advanced/swagger/generateSpec.ts +0 -105
- package/src/advanced/swagger/generateSummary.ts +0 -24
- package/src/advanced/swagger/generateSwaggerUI.ts +0 -70
- package/src/advanced/swagger/generateThemeCss.ts +0 -53
- package/src/advanced/swagger/index.ts +0 -25
- package/src/advanced/swagger/swagger.ts +0 -237
- package/src/advanced/swagger/types.ts +0 -206
- package/src/advanced/swagger/zodFieldToOpenAPI.ts +0 -94
- package/src/advanced/swagger/zodSchemaToOpenAPI.ts +0 -50
- package/src/advanced/swagger/zodToOpenAPI.ts +0 -22
- package/src/advanced/testing/factory.ts +0 -509
- package/src/advanced/testing/harness.ts +0 -612
- package/src/advanced/testing/index.ts +0 -430
- package/src/advanced/testing/load-test.ts +0 -618
- package/src/advanced/testing/mock-server.ts +0 -498
- package/src/advanced/testing/mock.ts +0 -670
- package/src/cli/bin.ts +0 -9
- package/src/cli/cli.ts +0 -158
- package/src/cli/commands/add.ts +0 -178
- package/src/cli/commands/build.ts +0 -73
- package/src/cli/commands/create.ts +0 -166
- package/src/cli/commands/dev.ts +0 -85
- package/src/cli/commands/generate.ts +0 -99
- package/src/cli/commands/help.ts +0 -95
- package/src/cli/commands/init.ts +0 -91
- package/src/cli/commands/version.ts +0 -38
- package/src/cli/index.ts +0 -6
- package/src/cli/templates/generators.ts +0 -359
- package/src/cli/templates/index.ts +0 -680
- package/src/cli/utils/exec.ts +0 -52
- package/src/cli/utils/file-system.ts +0 -78
- package/src/cli/utils/logger.ts +0 -111
- package/src/core/adapter.ts +0 -88
- package/src/core/application.ts +0 -1335
- package/src/core/context-pool.ts +0 -127
- package/src/core/context.ts +0 -412
- package/src/core/index.ts +0 -80
- package/src/core/middleware.ts +0 -262
- package/src/core/performance/buffer-pool.ts +0 -108
- package/src/core/performance/middleware-optimizer.ts +0 -162
- package/src/core/plugin/PluginManager.ts +0 -435
- package/src/core/plugin/builder.ts +0 -358
- package/src/core/plugin/index.ts +0 -50
- package/src/core/plugin/types.ts +0 -214
- package/src/core/router/file-router.ts +0 -594
- package/src/core/router/index.ts +0 -227
- package/src/core/router/radix-tree.ts +0 -226
- package/src/core/store/index.ts +0 -30
- package/src/core/store/registry.ts +0 -178
- package/src/core/store/request-store.ts +0 -240
- package/src/core/store/types.ts +0 -233
- package/src/core/types.ts +0 -574
- package/src/database/adapter.ts +0 -35
- package/src/database/adapters/index.ts +0 -1
- package/src/database/adapters/mysql.ts +0 -669
- package/src/database/database.ts +0 -70
- package/src/database/dialect.ts +0 -388
- package/src/database/index.ts +0 -12
- package/src/database/migrations.ts +0 -86
- package/src/database/optimizer.ts +0 -125
- package/src/database/query-builder.ts +0 -404
- package/src/database/realtime.ts +0 -53
- package/src/database/schema.ts +0 -71
- package/src/database/transactions.ts +0 -56
- package/src/database/types.ts +0 -87
- package/src/deployment/cluster.ts +0 -471
- package/src/deployment/config.ts +0 -454
- package/src/deployment/docker.ts +0 -599
- package/src/deployment/graceful-shutdown.ts +0 -373
- package/src/deployment/index.ts +0 -56
- package/src/index.ts +0 -264
- package/src/security/adapter.ts +0 -318
- package/src/security/auth/JWTPlugin.ts +0 -234
- package/src/security/auth/JWTProvider.ts +0 -316
- package/src/security/auth/adapter.ts +0 -12
- package/src/security/auth/jwt.ts +0 -234
- package/src/security/auth/middleware.ts +0 -188
- package/src/security/csrf.ts +0 -220
- package/src/security/headers.ts +0 -108
- package/src/security/index.ts +0 -60
- package/src/security/rate-limit/adapter.ts +0 -7
- package/src/security/rate-limit/memory.ts +0 -108
- package/src/security/rate-limit/middleware.ts +0 -181
- package/src/security/sanitization.ts +0 -75
- package/src/security/types.ts +0 -240
- package/src/security/utils.ts +0 -52
- package/tsconfig.json +0 -39
|
@@ -1,1287 +0,0 @@
|
|
|
1
|
-
# Testing Utilities
|
|
2
|
-
|
|
3
|
-
Nexus framework menyediakan comprehensive testing toolkit untuk memudahkan testing aplikasi. Toolkit ini mencakup test client, mock utilities, data factory, mock server, dan load testing capabilities.
|
|
4
|
-
|
|
5
|
-
## Table of Contents
|
|
6
|
-
|
|
7
|
-
1. [Overview](#overview)
|
|
8
|
-
2. [Test Client](#test-client)
|
|
9
|
-
3. [Data Factory](#data-factory)
|
|
10
|
-
4. [Mock Utilities](#mock-utilities)
|
|
11
|
-
5. [Mock Server](#mock-server)
|
|
12
|
-
6. [Load Testing](#load-testing)
|
|
13
|
-
7. [Test Suite Setup](#test-suite-setup)
|
|
14
|
-
8. [Database Seeding](#database-seeding)
|
|
15
|
-
9. [Examples](#examples)
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## Overview
|
|
20
|
-
|
|
21
|
-
Testing utilities tersedia di `src/advanced/testing/` dan terdiri dari:
|
|
22
|
-
|
|
23
|
-
- **`harness.ts`** - Test client, suite, dan assertion helpers
|
|
24
|
-
- **`factory.ts`** - Data factory dengan built-in generators
|
|
25
|
-
- **`mock.ts`** - Mock functions, database, fetch, timers
|
|
26
|
-
- **`mock-server.ts`** - Mock HTTP server untuk testing external APIs
|
|
27
|
-
- **`load-test.ts`** - Load testing dan stress testing utilities
|
|
28
|
-
|
|
29
|
-
### Import
|
|
30
|
-
|
|
31
|
-
```typescript
|
|
32
|
-
import {
|
|
33
|
-
TestClient,
|
|
34
|
-
createTestSuite,
|
|
35
|
-
TestSeeder,
|
|
36
|
-
expectDuration,
|
|
37
|
-
expectSnapshot,
|
|
38
|
-
waitFor,
|
|
39
|
-
retry,
|
|
40
|
-
spyOn,
|
|
41
|
-
// Factory
|
|
42
|
-
defineFactory,
|
|
43
|
-
generators,
|
|
44
|
-
// Mock
|
|
45
|
-
createMockFn,
|
|
46
|
-
MockDatabase,
|
|
47
|
-
MockFetch,
|
|
48
|
-
MockTimers,
|
|
49
|
-
EventSpy,
|
|
50
|
-
// Mock Server
|
|
51
|
-
createMockServer,
|
|
52
|
-
mockResponses,
|
|
53
|
-
// Load Test
|
|
54
|
-
createLoadTest,
|
|
55
|
-
formatLoadTestResults
|
|
56
|
-
} from '../advanced/testing';
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
---
|
|
60
|
-
|
|
61
|
-
## Test Client
|
|
62
|
-
|
|
63
|
-
`TestClient` adalah HTTP client untuk testing API endpoints dengan support untuk authentication, headers, dan various HTTP methods.
|
|
64
|
-
|
|
65
|
-
### Basic Usage
|
|
66
|
-
|
|
67
|
-
```typescript
|
|
68
|
-
import { TestClient } from '../advanced/testing';
|
|
69
|
-
import { app } from './app';
|
|
70
|
-
|
|
71
|
-
const client = new TestClient(app);
|
|
72
|
-
|
|
73
|
-
// GET request
|
|
74
|
-
const response = await client.get('/api/users');
|
|
75
|
-
console.log(response.status); // 200
|
|
76
|
-
console.log(response.json()); // parsed JSON
|
|
77
|
-
|
|
78
|
-
// POST request
|
|
79
|
-
const createResponse = await client.post('/api/users', {
|
|
80
|
-
body: { name: 'John', email: 'john@example.com' }
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
// PUT request
|
|
84
|
-
const updateResponse = await client.put('/api/users/1', {
|
|
85
|
-
body: { name: 'Jane' }
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
// DELETE request
|
|
89
|
-
const deleteResponse = await client.delete('/api/users/1');
|
|
90
|
-
|
|
91
|
-
// PATCH request
|
|
92
|
-
const patchResponse = await client.patch('/api/users/1', {
|
|
93
|
-
body: { active: true }
|
|
94
|
-
});
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
### Query Parameters
|
|
98
|
-
|
|
99
|
-
```typescript
|
|
100
|
-
const response = await client.get('/api/users', {
|
|
101
|
-
query: {
|
|
102
|
-
page: 1,
|
|
103
|
-
limit: 10,
|
|
104
|
-
search: 'john'
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### Custom Headers
|
|
110
|
-
|
|
111
|
-
```typescript
|
|
112
|
-
// Set default headers for all requests
|
|
113
|
-
client.setDefaultHeaders({
|
|
114
|
-
'X-API-Key': 'secret-key',
|
|
115
|
-
'Accept-Language': 'en-US'
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
// Or set headers per request
|
|
119
|
-
const response = await client.get('/api/data', {
|
|
120
|
-
headers: {
|
|
121
|
-
'X-Request-ID': 'req-123'
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### Authentication
|
|
127
|
-
|
|
128
|
-
```typescript
|
|
129
|
-
// Authenticate with token
|
|
130
|
-
client.authenticate('eyJhbGc...');
|
|
131
|
-
|
|
132
|
-
// Authenticate dengan user object
|
|
133
|
-
client.authenticate({
|
|
134
|
-
token: 'eyJhbGc...',
|
|
135
|
-
id: 1,
|
|
136
|
-
email: 'user@example.com'
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
// Get authenticated user
|
|
140
|
-
const user = client.getAuthUser();
|
|
141
|
-
|
|
142
|
-
// Clear authentication
|
|
143
|
-
client.clearAuth();
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
### Response Object
|
|
147
|
-
|
|
148
|
-
```typescript
|
|
149
|
-
interface TestResponse<T = any> {
|
|
150
|
-
status: number;
|
|
151
|
-
headers: Record<string, string | string[]>;
|
|
152
|
-
body: string;
|
|
153
|
-
duration: number;
|
|
154
|
-
json(): T;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const response = await client.get('/api/users/1');
|
|
158
|
-
console.log(response.status); // 200
|
|
159
|
-
console.log(response.duration); // milliseconds
|
|
160
|
-
console.log(response.json()); // parsed body
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
### Client Lifecycle
|
|
164
|
-
|
|
165
|
-
```typescript
|
|
166
|
-
const client = new TestClient(app);
|
|
167
|
-
|
|
168
|
-
// Start server (automatic, tapi bisa explicit)
|
|
169
|
-
await client.start();
|
|
170
|
-
|
|
171
|
-
// Use client...
|
|
172
|
-
const response = await client.get('/api/users');
|
|
173
|
-
|
|
174
|
-
// Stop server
|
|
175
|
-
await client.stop();
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
---
|
|
179
|
-
|
|
180
|
-
## Data Factory
|
|
181
|
-
|
|
182
|
-
Data factory untuk generate realistic test data dengan support untuk traits, sequences, dan custom generators.
|
|
183
|
-
|
|
184
|
-
### Defining a Factory
|
|
185
|
-
|
|
186
|
-
```typescript
|
|
187
|
-
import { defineFactory, generators } from '../advanced/testing';
|
|
188
|
-
|
|
189
|
-
const userFactory = defineFactory({
|
|
190
|
-
id: generators.uuid(),
|
|
191
|
-
email: generators.email(),
|
|
192
|
-
firstName: generators.firstName(),
|
|
193
|
-
lastName: generators.lastName(),
|
|
194
|
-
username: generators.username(),
|
|
195
|
-
password: generators.password(),
|
|
196
|
-
phone: generators.phone(),
|
|
197
|
-
createdAt: generators.pastDate(365)
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
// Build a single record (in-memory, tidak disimpan)
|
|
201
|
-
const user = userFactory.build();
|
|
202
|
-
// {
|
|
203
|
-
// id: 'uuid-123',
|
|
204
|
-
// email: 'user_1@example.com',
|
|
205
|
-
// firstName: 'John',
|
|
206
|
-
// ...
|
|
207
|
-
// }
|
|
208
|
-
|
|
209
|
-
// Build banyak records
|
|
210
|
-
const users = userFactory.buildMany(5);
|
|
211
|
-
|
|
212
|
-
// Build dengan overrides
|
|
213
|
-
const adminUser = userFactory.build({
|
|
214
|
-
overrides: {
|
|
215
|
-
email: 'admin@example.com',
|
|
216
|
-
role: 'admin'
|
|
217
|
-
}
|
|
218
|
-
});
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
### Traits
|
|
222
|
-
|
|
223
|
-
Traits adalah variations dari factory yang bisa di-apply:
|
|
224
|
-
|
|
225
|
-
```typescript
|
|
226
|
-
const userFactory = defineFactory({
|
|
227
|
-
id: generators.uuid(),
|
|
228
|
-
email: generators.email(),
|
|
229
|
-
role: 'user',
|
|
230
|
-
active: true
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
userFactory.trait('admin', {
|
|
234
|
-
role: 'admin'
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
userFactory.trait('inactive', {
|
|
238
|
-
active: false
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
// Use traits
|
|
242
|
-
const admin = userFactory.build({ traits: ['admin'] });
|
|
243
|
-
const inactiveUser = userFactory.build({ traits: ['inactive'] });
|
|
244
|
-
const inactiveAdmin = userFactory.build({ traits: ['admin', 'inactive'] });
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
### Persistence
|
|
248
|
-
|
|
249
|
-
```typescript
|
|
250
|
-
// Set persist function untuk save ke database
|
|
251
|
-
userFactory.setPersist(async (user) => {
|
|
252
|
-
return db.users.create(user);
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
// Create dengan persistence
|
|
256
|
-
const savedUser = await userFactory.create();
|
|
257
|
-
|
|
258
|
-
// Create banyak dengan persistence
|
|
259
|
-
const savedUsers = await userFactory.createMany(10);
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
### After Create Hooks
|
|
263
|
-
|
|
264
|
-
```typescript
|
|
265
|
-
userFactory.afterCreate(async (user) => {
|
|
266
|
-
// Create related data
|
|
267
|
-
await db.profiles.create({
|
|
268
|
-
userId: user.id,
|
|
269
|
-
bio: 'New user'
|
|
270
|
-
});
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
const user = await userFactory.create();
|
|
274
|
-
// User dan profile akan dibuat
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
### Built-in Generators
|
|
278
|
-
|
|
279
|
-
#### UUID dan ID
|
|
280
|
-
|
|
281
|
-
```typescript
|
|
282
|
-
generators.uuid() // Random UUID
|
|
283
|
-
generators.sequence('USER_') // USER_1, USER_2, ...
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
#### Numbers
|
|
287
|
-
|
|
288
|
-
```typescript
|
|
289
|
-
generators.integer(0, 100) // Random integer
|
|
290
|
-
generators.float(0, 100, 2) // Random float with decimals
|
|
291
|
-
generators.boolean(0.7) // 70% chance true
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
#### Text
|
|
295
|
-
|
|
296
|
-
```typescript
|
|
297
|
-
generators.string(10) // Random string (10 chars)
|
|
298
|
-
generators.lorem(10) // 10 random lorem words
|
|
299
|
-
generators.paragraph(3) // 3 sentences
|
|
300
|
-
generators.slug() // url-slug-123-abc
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
#### Person Data
|
|
304
|
-
|
|
305
|
-
```typescript
|
|
306
|
-
generators.firstName() // 'John'
|
|
307
|
-
generators.lastName() // 'Smith'
|
|
308
|
-
generators.fullName() // 'John Smith'
|
|
309
|
-
generators.email() // 'user_1@example.com'
|
|
310
|
-
generators.username() // 'user_1_abc'
|
|
311
|
-
generators.phone() // '123-456-7890'
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
#### Company Data
|
|
315
|
-
|
|
316
|
-
```typescript
|
|
317
|
-
generators.company() // 'TechCorp Solutions'
|
|
318
|
-
generators.jobTitle() // 'Senior Developer'
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
#### Dates
|
|
322
|
-
|
|
323
|
-
```typescript
|
|
324
|
-
generators.date(start, end) // Random date between start and end
|
|
325
|
-
generators.pastDate(365) // Date up to 365 days ago
|
|
326
|
-
generators.futureDate(365) // Date up to 365 days in future
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
#### Web/Network
|
|
330
|
-
|
|
331
|
-
```typescript
|
|
332
|
-
generators.url() // 'https://example.com/abc123'
|
|
333
|
-
generators.ipv4() // '192.168.1.1'
|
|
334
|
-
generators.slug() // 'slug-1-abc'
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
#### Commerce
|
|
338
|
-
|
|
339
|
-
```typescript
|
|
340
|
-
generators.price(1, 1000) // Random price
|
|
341
|
-
generators.currency() // 'USD'
|
|
342
|
-
generators.countryCode() // 'US'
|
|
343
|
-
generators.address() // { street, city, state, zip }
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
#### Media
|
|
347
|
-
|
|
348
|
-
```typescript
|
|
349
|
-
generators.hexColor() // '#a1b2c3'
|
|
350
|
-
generators.filePath() // '/documents/abc.pdf'
|
|
351
|
-
generators.mimeType() // 'application/json'
|
|
352
|
-
generators.creditCard() // '1234-5678-9012-3456'
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
#### Collections
|
|
356
|
-
|
|
357
|
-
```typescript
|
|
358
|
-
generators.oneOf(['a', 'b', 'c']) // Pick one random
|
|
359
|
-
generators.someOf(['a', 'b', 'c'], 1, 2) // Pick 1-2 items
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
---
|
|
363
|
-
|
|
364
|
-
## Mock Utilities
|
|
365
|
-
|
|
366
|
-
Utilities untuk mock functions, database, fetch, timers, dan events.
|
|
367
|
-
|
|
368
|
-
### Mock Functions
|
|
369
|
-
|
|
370
|
-
```typescript
|
|
371
|
-
import { createMockFn } from '../advanced/testing';
|
|
372
|
-
|
|
373
|
-
const mockFn = createMockFn();
|
|
374
|
-
|
|
375
|
-
// Set return value
|
|
376
|
-
mockFn.mockReturnValue('hello');
|
|
377
|
-
mockFn(); // 'hello'
|
|
378
|
-
|
|
379
|
-
// Set return value for one call
|
|
380
|
-
mockFn.mockReturnValueOnce('first');
|
|
381
|
-
mockFn.mockReturnValue('second');
|
|
382
|
-
mockFn(); // 'first'
|
|
383
|
-
mockFn(); // 'second'
|
|
384
|
-
|
|
385
|
-
// Async mocking
|
|
386
|
-
mockFn.mockResolvedValue({ id: 1 });
|
|
387
|
-
await mockFn(); // { id: 1 }
|
|
388
|
-
|
|
389
|
-
// Async reject
|
|
390
|
-
mockFn.mockRejectedValue(new Error('Failed'));
|
|
391
|
-
try {
|
|
392
|
-
await mockFn();
|
|
393
|
-
} catch (error) {
|
|
394
|
-
console.log(error.message); // 'Failed'
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// Custom implementation
|
|
398
|
-
mockFn.mockImplementation((x) => x * 2);
|
|
399
|
-
mockFn(5); // 10
|
|
400
|
-
|
|
401
|
-
// Assertions
|
|
402
|
-
if (mockFn.toHaveBeenCalled()) {
|
|
403
|
-
console.log('Function was called');
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
if (mockFn.toHaveBeenCalledWith(5)) {
|
|
407
|
-
console.log('Called with 5');
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
console.log(mockFn.calls); // Array of all calls
|
|
411
|
-
console.log(mockFn.results); // Array of all results
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
### Mock Database
|
|
415
|
-
|
|
416
|
-
```typescript
|
|
417
|
-
import { MockDatabase } from '../advanced/testing';
|
|
418
|
-
|
|
419
|
-
const mockDb = new MockDatabase();
|
|
420
|
-
const users = mockDb.table('users');
|
|
421
|
-
|
|
422
|
-
// Insert
|
|
423
|
-
const user = await users.insert({ name: 'John', email: 'john@example.com' });
|
|
424
|
-
|
|
425
|
-
// Find
|
|
426
|
-
const found = await users.find(user.id);
|
|
427
|
-
|
|
428
|
-
// Find many
|
|
429
|
-
const all = await users.findMany();
|
|
430
|
-
const filtered = await users.findMany({ name: 'John' });
|
|
431
|
-
|
|
432
|
-
// Update
|
|
433
|
-
const updated = await users.update(user.id, { name: 'Jane' });
|
|
434
|
-
|
|
435
|
-
// Delete
|
|
436
|
-
const deleted = await users.delete(user.id);
|
|
437
|
-
|
|
438
|
-
// Seed data
|
|
439
|
-
users.seed([
|
|
440
|
-
{ id: 1, name: 'John', email: 'john@example.com' },
|
|
441
|
-
{ id: 2, name: 'Jane', email: 'jane@example.com' }
|
|
442
|
-
]);
|
|
443
|
-
|
|
444
|
-
// Get all records
|
|
445
|
-
const records = users.getAll();
|
|
446
|
-
|
|
447
|
-
// Clear data
|
|
448
|
-
users.clear();
|
|
449
|
-
|
|
450
|
-
// Reset (clear data + reset mocks)
|
|
451
|
-
users.reset();
|
|
452
|
-
|
|
453
|
-
// Assertions on mock calls
|
|
454
|
-
console.log(users.insert.calls); // Semua calls ke insert()
|
|
455
|
-
console.log(users.update.calls); // Semua calls ke update()
|
|
456
|
-
```
|
|
457
|
-
|
|
458
|
-
### Mock Fetch
|
|
459
|
-
|
|
460
|
-
```typescript
|
|
461
|
-
import { mockFetch } from '../advanced/testing';
|
|
462
|
-
|
|
463
|
-
// Register mock routes
|
|
464
|
-
mockFetch
|
|
465
|
-
.get('/api/users')
|
|
466
|
-
.reply(200, [{ id: 1, name: 'John' }]);
|
|
467
|
-
|
|
468
|
-
mockFetch
|
|
469
|
-
.post('/api/users')
|
|
470
|
-
.reply(201, { id: 2, name: 'Jane' });
|
|
471
|
-
|
|
472
|
-
mockFetch
|
|
473
|
-
.delete('/api/users/:id')
|
|
474
|
-
.reply(204);
|
|
475
|
-
|
|
476
|
-
// Install globally
|
|
477
|
-
mockFetch.install();
|
|
478
|
-
|
|
479
|
-
// Use fetch normally
|
|
480
|
-
const response = await fetch('/api/users');
|
|
481
|
-
const data = await response.json();
|
|
482
|
-
|
|
483
|
-
// Restore original fetch
|
|
484
|
-
mockFetch.restore();
|
|
485
|
-
|
|
486
|
-
// Assertions
|
|
487
|
-
if (mockFetch.wasCalled('/api/users', 'GET')) {
|
|
488
|
-
console.log('GET /api/users was called');
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
console.log(mockFetch.getCalls()); // All fetch calls
|
|
492
|
-
```
|
|
493
|
-
|
|
494
|
-
### Mock Fetch dengan Custom Handler
|
|
495
|
-
|
|
496
|
-
```typescript
|
|
497
|
-
mockFetch
|
|
498
|
-
.post('/api/users')
|
|
499
|
-
.replyWith(async (url, options) => {
|
|
500
|
-
const body = JSON.parse(options?.body || '{}');
|
|
501
|
-
return {
|
|
502
|
-
status: 201,
|
|
503
|
-
body: { id: Math.random(), ...body }
|
|
504
|
-
};
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
// Simulate errors
|
|
508
|
-
mockFetch
|
|
509
|
-
.get('/api/error')
|
|
510
|
-
.networkError('Connection refused');
|
|
511
|
-
|
|
512
|
-
mockFetch
|
|
513
|
-
.get('/api/timeout')
|
|
514
|
-
.timeout(5000);
|
|
515
|
-
```
|
|
516
|
-
|
|
517
|
-
### Mock Timers
|
|
518
|
-
|
|
519
|
-
```typescript
|
|
520
|
-
import { mockTimers } from '../advanced/testing';
|
|
521
|
-
|
|
522
|
-
// Install mock timers
|
|
523
|
-
mockTimers.install();
|
|
524
|
-
|
|
525
|
-
// Set current time
|
|
526
|
-
mockTimers.setTime(Date.now());
|
|
527
|
-
|
|
528
|
-
// Use setTimeout normally
|
|
529
|
-
let called = false;
|
|
530
|
-
setTimeout(() => { called = true; }, 1000);
|
|
531
|
-
|
|
532
|
-
// Advance time
|
|
533
|
-
await mockTimers.tick(1000);
|
|
534
|
-
console.log(called); // true
|
|
535
|
-
|
|
536
|
-
// Run all pending timers
|
|
537
|
-
await mockTimers.runAll();
|
|
538
|
-
|
|
539
|
-
// Get current mocked time
|
|
540
|
-
console.log(mockTimers.now());
|
|
541
|
-
|
|
542
|
-
// Restore
|
|
543
|
-
mockTimers.restore();
|
|
544
|
-
```
|
|
545
|
-
|
|
546
|
-
### Event Spy
|
|
547
|
-
|
|
548
|
-
```typescript
|
|
549
|
-
import { EventSpy } from '../advanced/testing';
|
|
550
|
-
import { EventEmitter } from 'events';
|
|
551
|
-
|
|
552
|
-
const emitter = new EventEmitter();
|
|
553
|
-
const spy = new EventSpy(emitter);
|
|
554
|
-
|
|
555
|
-
// Spy on events
|
|
556
|
-
spy.on('data').on('error').on('complete');
|
|
557
|
-
|
|
558
|
-
// Emit events
|
|
559
|
-
emitter.emit('data', { id: 1 });
|
|
560
|
-
emitter.emit('data', { id: 2 });
|
|
561
|
-
emitter.emit('complete');
|
|
562
|
-
|
|
563
|
-
// Assertions
|
|
564
|
-
if (spy.wasEmitted('data')) {
|
|
565
|
-
console.log('Data event was emitted');
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
console.log(spy.count('data')); // 2
|
|
569
|
-
console.log(spy.getCalls('data')); // [[{ id: 1 }], [{ id: 2 }]]
|
|
570
|
-
|
|
571
|
-
// Clear
|
|
572
|
-
spy.clear();
|
|
573
|
-
```
|
|
574
|
-
|
|
575
|
-
---
|
|
576
|
-
|
|
577
|
-
## Mock Server
|
|
578
|
-
|
|
579
|
-
Mock HTTP server untuk testing external API calls.
|
|
580
|
-
|
|
581
|
-
### Basic Setup
|
|
582
|
-
|
|
583
|
-
```typescript
|
|
584
|
-
import { createMockServer } from '../advanced/testing';
|
|
585
|
-
|
|
586
|
-
const mockServer = createMockServer();
|
|
587
|
-
|
|
588
|
-
// Register routes
|
|
589
|
-
mockServer.get('/api/users', (req) => ({
|
|
590
|
-
status: 200,
|
|
591
|
-
body: [{ id: 1, name: 'John' }]
|
|
592
|
-
}));
|
|
593
|
-
|
|
594
|
-
mockServer.post('/api/users', (req) => ({
|
|
595
|
-
status: 201,
|
|
596
|
-
body: { id: 2, ...req.body }
|
|
597
|
-
}));
|
|
598
|
-
|
|
599
|
-
// Start server
|
|
600
|
-
const url = await mockServer.start(3001);
|
|
601
|
-
console.log(url); // 'http://127.0.0.1:3001'
|
|
602
|
-
|
|
603
|
-
// Use it...
|
|
604
|
-
const response = await fetch(`${url}/api/users`);
|
|
605
|
-
|
|
606
|
-
// Stop server
|
|
607
|
-
await mockServer.stop();
|
|
608
|
-
```
|
|
609
|
-
|
|
610
|
-
### Route Patterns
|
|
611
|
-
|
|
612
|
-
```typescript
|
|
613
|
-
// Exact path
|
|
614
|
-
mockServer.get('/api/users', handler);
|
|
615
|
-
|
|
616
|
-
// Path parameters
|
|
617
|
-
mockServer.get('/api/users/:id', (req) => {
|
|
618
|
-
console.log(req.params.id); // '123'
|
|
619
|
-
return { status: 200, body: { id: req.params.id } };
|
|
620
|
-
});
|
|
621
|
-
|
|
622
|
-
// Regex pattern
|
|
623
|
-
mockServer.get(/^\/api\/v\d+\/users/, handler);
|
|
624
|
-
|
|
625
|
-
// Any method
|
|
626
|
-
mockServer.any('/webhook', handler);
|
|
627
|
-
```
|
|
628
|
-
|
|
629
|
-
### Request Object
|
|
630
|
-
|
|
631
|
-
```typescript
|
|
632
|
-
mockServer.post('/api/data', (req) => {
|
|
633
|
-
console.log(req.method); // 'POST'
|
|
634
|
-
console.log(req.path); // '/api/data'
|
|
635
|
-
console.log(req.query); // { page: '1', limit: '10' }
|
|
636
|
-
console.log(req.headers); // { 'content-type': 'application/json' }
|
|
637
|
-
console.log(req.body); // Parsed JSON atau raw string
|
|
638
|
-
console.log(req.params); // URL parameters dari path pattern
|
|
639
|
-
|
|
640
|
-
return { status: 200 };
|
|
641
|
-
});
|
|
642
|
-
```
|
|
643
|
-
|
|
644
|
-
### Response Helpers
|
|
645
|
-
|
|
646
|
-
```typescript
|
|
647
|
-
import { mockResponses } from '../advanced/testing';
|
|
648
|
-
|
|
649
|
-
mockServer.get('/ok', mockResponses.ok());
|
|
650
|
-
mockServer.post('/created', mockResponses.created({ id: 1 }));
|
|
651
|
-
mockServer.get('/no-content', mockResponses.noContent());
|
|
652
|
-
mockServer.get('/error', mockResponses.badRequest('Invalid input'));
|
|
653
|
-
mockServer.get('/unauthorized', mockResponses.unauthorized());
|
|
654
|
-
mockServer.get('/forbidden', mockResponses.forbidden());
|
|
655
|
-
mockServer.get('/not-found', mockResponses.notFound());
|
|
656
|
-
mockServer.get('/error', mockResponses.serverError());
|
|
657
|
-
|
|
658
|
-
// Echo request back
|
|
659
|
-
mockServer.get('/echo', mockResponses.echo());
|
|
660
|
-
|
|
661
|
-
// Paginated response
|
|
662
|
-
const items = Array.from({ length: 50 }, (_, i) => ({ id: i + 1 }));
|
|
663
|
-
mockServer.get('/items', mockResponses.paginated(items, 10));
|
|
664
|
-
```
|
|
665
|
-
|
|
666
|
-
### Route Configuration
|
|
667
|
-
|
|
668
|
-
```typescript
|
|
669
|
-
mockServer
|
|
670
|
-
.get('/api/data')
|
|
671
|
-
.delay(500) // Delay 500ms before response
|
|
672
|
-
.times(1) // Respond only once
|
|
673
|
-
.reply(200, { data: 'value' });
|
|
674
|
-
|
|
675
|
-
// Handler for dynamic responses
|
|
676
|
-
mockServer
|
|
677
|
-
.post('/api/users')
|
|
678
|
-
.replyWith(async (req) => {
|
|
679
|
-
if (!req.body.email) {
|
|
680
|
-
return { status: 400, body: { error: 'Email required' } };
|
|
681
|
-
}
|
|
682
|
-
return { status: 201, body: { id: 1, ...req.body } };
|
|
683
|
-
});
|
|
684
|
-
```
|
|
685
|
-
|
|
686
|
-
### Assertions
|
|
687
|
-
|
|
688
|
-
```typescript
|
|
689
|
-
const mockServer = createMockServer();
|
|
690
|
-
|
|
691
|
-
// ... register routes and use them
|
|
692
|
-
|
|
693
|
-
// Check if path was called
|
|
694
|
-
if (mockServer.wasCalled('/api/users', 'GET')) {
|
|
695
|
-
console.log('GET /api/users was called');
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
// Get call count
|
|
699
|
-
const count = mockServer.callCount('/api/users', 'GET');
|
|
700
|
-
console.log(`Called ${count} times`);
|
|
701
|
-
|
|
702
|
-
// Get all requests
|
|
703
|
-
const requests = mockServer.getRequests();
|
|
704
|
-
requests.forEach(req => {
|
|
705
|
-
console.log(`${req.method} ${req.path}`);
|
|
706
|
-
});
|
|
707
|
-
|
|
708
|
-
// Clear request history
|
|
709
|
-
mockServer.clearRequests();
|
|
710
|
-
|
|
711
|
-
// Reset everything
|
|
712
|
-
mockServer.reset();
|
|
713
|
-
```
|
|
714
|
-
|
|
715
|
-
---
|
|
716
|
-
|
|
717
|
-
## Load Testing
|
|
718
|
-
|
|
719
|
-
Utilities untuk load testing dan stress testing APIs.
|
|
720
|
-
|
|
721
|
-
### Basic Load Test
|
|
722
|
-
|
|
723
|
-
```typescript
|
|
724
|
-
import { createLoadTest, formatLoadTestResults } from '../advanced/testing';
|
|
725
|
-
|
|
726
|
-
const loadTest = createLoadTest({
|
|
727
|
-
baseUrl: 'http://localhost:3000',
|
|
728
|
-
vus: 10, // 10 virtual users
|
|
729
|
-
duration: '30s' // 30 seconds
|
|
730
|
-
});
|
|
731
|
-
|
|
732
|
-
// Register test function
|
|
733
|
-
loadTest.test('default', async (vu, http) => {
|
|
734
|
-
const response = await http.get('/api/users');
|
|
735
|
-
if (response.status !== 200) {
|
|
736
|
-
throw new Error(`Expected 200, got ${response.status}`);
|
|
737
|
-
}
|
|
738
|
-
});
|
|
739
|
-
|
|
740
|
-
// Run test
|
|
741
|
-
const results = await loadTest.run();
|
|
742
|
-
|
|
743
|
-
// Format and print results
|
|
744
|
-
console.log(formatLoadTestResults(results));
|
|
745
|
-
```
|
|
746
|
-
|
|
747
|
-
### Scenarios
|
|
748
|
-
|
|
749
|
-
```typescript
|
|
750
|
-
const loadTest = createLoadTest({
|
|
751
|
-
baseUrl: 'http://localhost:3000',
|
|
752
|
-
scenarios: {
|
|
753
|
-
'ramp-up': {
|
|
754
|
-
name: 'Ramping up',
|
|
755
|
-
executor: 'ramping-vus',
|
|
756
|
-
startVUs: 0,
|
|
757
|
-
stages: [
|
|
758
|
-
{ duration: '10s', target: 10 },
|
|
759
|
-
{ duration: '20s', target: 50 },
|
|
760
|
-
{ duration: '10s', target: 0 }
|
|
761
|
-
],
|
|
762
|
-
exec: 'load-test'
|
|
763
|
-
},
|
|
764
|
-
'spike': {
|
|
765
|
-
name: 'Spike test',
|
|
766
|
-
executor: 'constant-vus',
|
|
767
|
-
vus: 100,
|
|
768
|
-
duration: '5s',
|
|
769
|
-
exec: 'load-test'
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
});
|
|
773
|
-
|
|
774
|
-
loadTest.test('load-test', async (vu, http) => {
|
|
775
|
-
await http.get('/api/users');
|
|
776
|
-
});
|
|
777
|
-
|
|
778
|
-
const results = await loadTest.run();
|
|
779
|
-
```
|
|
780
|
-
|
|
781
|
-
### Thresholds
|
|
782
|
-
|
|
783
|
-
```typescript
|
|
784
|
-
const loadTest = createLoadTest({
|
|
785
|
-
baseUrl: 'http://localhost:3000',
|
|
786
|
-
vus: 10,
|
|
787
|
-
duration: '30s',
|
|
788
|
-
thresholds: {
|
|
789
|
-
'http_req_duration': [
|
|
790
|
-
'p(95) < 500', // 95% requests under 500ms
|
|
791
|
-
'p(99) < 1000', // 99% requests under 1000ms
|
|
792
|
-
'avg < 200' // average under 200ms
|
|
793
|
-
],
|
|
794
|
-
'http_req_failed': [
|
|
795
|
-
'rate < 0.01' // Less than 1% error rate
|
|
796
|
-
],
|
|
797
|
-
'http_reqs': [
|
|
798
|
-
'rate > 100' // At least 100 requests per second
|
|
799
|
-
]
|
|
800
|
-
}
|
|
801
|
-
});
|
|
802
|
-
|
|
803
|
-
loadTest.test('load-test', async (vu, http) => {
|
|
804
|
-
await http.get('/api/users');
|
|
805
|
-
});
|
|
806
|
-
|
|
807
|
-
const results = await loadTest.run();
|
|
808
|
-
console.log(results.thresholdsPassed); // true or false
|
|
809
|
-
```
|
|
810
|
-
|
|
811
|
-
### HTTP Client
|
|
812
|
-
|
|
813
|
-
```typescript
|
|
814
|
-
loadTest.test('api-test', async (vu, http) => {
|
|
815
|
-
// GET request
|
|
816
|
-
const getResp = await http.get('/api/users', {
|
|
817
|
-
timeout: 5000
|
|
818
|
-
});
|
|
819
|
-
|
|
820
|
-
// POST request
|
|
821
|
-
const postResp = await http.post('/api/users', {
|
|
822
|
-
name: `User ${vu.id}`,
|
|
823
|
-
email: `user${vu.id}@example.com`
|
|
824
|
-
});
|
|
825
|
-
|
|
826
|
-
// PUT/DELETE requests
|
|
827
|
-
await http.put('/api/users/1', { name: 'Updated' });
|
|
828
|
-
await http.delete('/api/users/1');
|
|
829
|
-
|
|
830
|
-
// Set headers
|
|
831
|
-
http.setHeader('Authorization', `Bearer ${vu.data.token}`);
|
|
832
|
-
|
|
833
|
-
// Store data per VU
|
|
834
|
-
vu.data.userId = postResp.json().id;
|
|
835
|
-
});
|
|
836
|
-
```
|
|
837
|
-
|
|
838
|
-
### Response Object
|
|
839
|
-
|
|
840
|
-
```typescript
|
|
841
|
-
interface HttpResponse {
|
|
842
|
-
status: number;
|
|
843
|
-
headers: Record<string, string | string[]>;
|
|
844
|
-
body: string;
|
|
845
|
-
latency: number; // milliseconds
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
loadTest.test('api-test', async (vu, http) => {
|
|
849
|
-
const response = await http.get('/api/data');
|
|
850
|
-
console.log(response.status); // HTTP status
|
|
851
|
-
console.log(response.latency); // Response time in ms
|
|
852
|
-
console.log(response.body); // Response body
|
|
853
|
-
console.log(response.headers); // Response headers
|
|
854
|
-
});
|
|
855
|
-
```
|
|
856
|
-
|
|
857
|
-
### Results
|
|
858
|
-
|
|
859
|
-
```typescript
|
|
860
|
-
interface LoadTestResult {
|
|
861
|
-
totalRequests: number;
|
|
862
|
-
successfulRequests: number;
|
|
863
|
-
failedRequests: number;
|
|
864
|
-
duration: number; // milliseconds
|
|
865
|
-
requestsPerSecond: number;
|
|
866
|
-
latency: {
|
|
867
|
-
min: number;
|
|
868
|
-
max: number;
|
|
869
|
-
avg: number;
|
|
870
|
-
p50: number;
|
|
871
|
-
p90: number;
|
|
872
|
-
p95: number;
|
|
873
|
-
p99: number;
|
|
874
|
-
};
|
|
875
|
-
errorRate: number; // 0-1
|
|
876
|
-
thresholdsPassed: boolean;
|
|
877
|
-
thresholdResults: Record<string, any>;
|
|
878
|
-
errors: Array<{ message: string; count: number }>;
|
|
879
|
-
}
|
|
880
|
-
```
|
|
881
|
-
|
|
882
|
-
---
|
|
883
|
-
|
|
884
|
-
## Test Suite Setup
|
|
885
|
-
|
|
886
|
-
Helper untuk setup test suite dengan automatic lifecycle management.
|
|
887
|
-
|
|
888
|
-
### Basic Test Suite
|
|
889
|
-
|
|
890
|
-
```typescript
|
|
891
|
-
import { createTestSuite } from '../advanced/testing';
|
|
892
|
-
import { app } from './app';
|
|
893
|
-
|
|
894
|
-
const suite = createTestSuite({
|
|
895
|
-
app,
|
|
896
|
-
beforeAll: async () => {
|
|
897
|
-
// Setup: init database, seed data, etc
|
|
898
|
-
await db.migrate();
|
|
899
|
-
},
|
|
900
|
-
afterAll: async () => {
|
|
901
|
-
// Teardown: cleanup database, close connections, etc
|
|
902
|
-
await db.close();
|
|
903
|
-
},
|
|
904
|
-
beforeEach: async () => {
|
|
905
|
-
// Before each test: seed fresh data
|
|
906
|
-
await seedTestData();
|
|
907
|
-
},
|
|
908
|
-
afterEach: async () => {
|
|
909
|
-
// After each test: cleanup data
|
|
910
|
-
await db.truncate('users');
|
|
911
|
-
}
|
|
912
|
-
});
|
|
913
|
-
|
|
914
|
-
// In your test
|
|
915
|
-
const ctx = await suite.setup();
|
|
916
|
-
const response = await ctx.client.get('/api/users');
|
|
917
|
-
await suite.teardown();
|
|
918
|
-
```
|
|
919
|
-
|
|
920
|
-
---
|
|
921
|
-
|
|
922
|
-
## Database Seeding
|
|
923
|
-
|
|
924
|
-
Helper untuk seed database dengan test data.
|
|
925
|
-
|
|
926
|
-
### Test Seeder
|
|
927
|
-
|
|
928
|
-
```typescript
|
|
929
|
-
import { TestSeeder } from '../advanced/testing';
|
|
930
|
-
|
|
931
|
-
const seeder = new TestSeeder();
|
|
932
|
-
|
|
933
|
-
// Configure database functions
|
|
934
|
-
seeder.configure({
|
|
935
|
-
insert: async (table, data) => {
|
|
936
|
-
return db[table].insertMany(data);
|
|
937
|
-
},
|
|
938
|
-
truncate: async (table) => {
|
|
939
|
-
return db[table].truncate();
|
|
940
|
-
}
|
|
941
|
-
});
|
|
942
|
-
|
|
943
|
-
// Define seed data
|
|
944
|
-
seeder.define('users', [
|
|
945
|
-
{ id: 1, name: 'John', email: 'john@example.com' },
|
|
946
|
-
{ id: 2, name: 'Jane', email: 'jane@example.com' }
|
|
947
|
-
]);
|
|
948
|
-
|
|
949
|
-
seeder.define('posts', [
|
|
950
|
-
{ id: 1, userId: 1, title: 'Post 1' },
|
|
951
|
-
{ id: 2, userId: 2, title: 'Post 2' }
|
|
952
|
-
]);
|
|
953
|
-
|
|
954
|
-
// Seed specific table
|
|
955
|
-
await seeder.seed('users');
|
|
956
|
-
|
|
957
|
-
// Seed all defined tables
|
|
958
|
-
await seeder.seedAll();
|
|
959
|
-
|
|
960
|
-
// Seed dengan custom data
|
|
961
|
-
await seeder.seed('users', [
|
|
962
|
-
{ id: 3, name: 'Bob', email: 'bob@example.com' }
|
|
963
|
-
]);
|
|
964
|
-
|
|
965
|
-
// Truncate tables
|
|
966
|
-
await seeder.truncateAll();
|
|
967
|
-
```
|
|
968
|
-
|
|
969
|
-
---
|
|
970
|
-
|
|
971
|
-
## Assertion Helpers
|
|
972
|
-
|
|
973
|
-
### Performance Assertions
|
|
974
|
-
|
|
975
|
-
```typescript
|
|
976
|
-
import { expectDuration } from '../advanced/testing';
|
|
977
|
-
|
|
978
|
-
const response = await client.get('/api/data');
|
|
979
|
-
|
|
980
|
-
expectDuration(response).toBeLessThan(500); // < 500ms
|
|
981
|
-
expectDuration(response).toBeGreaterThan(10); // > 10ms
|
|
982
|
-
expectDuration(response).toBeBetween(50, 500); // 50-500ms
|
|
983
|
-
```
|
|
984
|
-
|
|
985
|
-
### Snapshot Testing
|
|
986
|
-
|
|
987
|
-
```typescript
|
|
988
|
-
import { expectSnapshot, updateSnapshots } from '../advanced/testing';
|
|
989
|
-
|
|
990
|
-
// Capture snapshot (first run)
|
|
991
|
-
const response = await client.get('/api/users');
|
|
992
|
-
expectSnapshot('users-list', response.json()).toMatchSnapshot();
|
|
993
|
-
|
|
994
|
-
// Update snapshots when needed
|
|
995
|
-
updateSnapshots(true);
|
|
996
|
-
expectSnapshot('users-list', newData).toMatchSnapshot();
|
|
997
|
-
updateSnapshots(false);
|
|
998
|
-
```
|
|
999
|
-
|
|
1000
|
-
### Utility Functions
|
|
1001
|
-
|
|
1002
|
-
```typescript
|
|
1003
|
-
import { waitFor, retry, spyOn } from '../advanced/testing';
|
|
1004
|
-
|
|
1005
|
-
// Wait for condition
|
|
1006
|
-
await waitFor(() => someCondition, {
|
|
1007
|
-
timeout: 5000,
|
|
1008
|
-
interval: 100
|
|
1009
|
-
});
|
|
1010
|
-
|
|
1011
|
-
// Retry operation
|
|
1012
|
-
const result = await retry(
|
|
1013
|
-
async () => await unreliableOperation(),
|
|
1014
|
-
{
|
|
1015
|
-
attempts: 3,
|
|
1016
|
-
delay: 100,
|
|
1017
|
-
backoff: true
|
|
1018
|
-
}
|
|
1019
|
-
);
|
|
1020
|
-
|
|
1021
|
-
// Spy on method
|
|
1022
|
-
const obj = { method: () => 'result' };
|
|
1023
|
-
const spy = spyOn(obj, 'method');
|
|
1024
|
-
obj.method();
|
|
1025
|
-
obj.method();
|
|
1026
|
-
console.log(spy.calls); // [[],[]]
|
|
1027
|
-
spy.restore();
|
|
1028
|
-
```
|
|
1029
|
-
|
|
1030
|
-
---
|
|
1031
|
-
|
|
1032
|
-
## Examples
|
|
1033
|
-
|
|
1034
|
-
### Example 1: API Testing
|
|
1035
|
-
|
|
1036
|
-
```typescript
|
|
1037
|
-
import { TestClient, defineFactory, generators } from '../advanced/testing';
|
|
1038
|
-
import { app } from './app';
|
|
1039
|
-
|
|
1040
|
-
describe('Users API', () => {
|
|
1041
|
-
let client: TestClient;
|
|
1042
|
-
|
|
1043
|
-
beforeAll(async () => {
|
|
1044
|
-
client = new TestClient(app);
|
|
1045
|
-
await client.start();
|
|
1046
|
-
});
|
|
1047
|
-
|
|
1048
|
-
afterAll(async () => {
|
|
1049
|
-
await client.stop();
|
|
1050
|
-
});
|
|
1051
|
-
|
|
1052
|
-
it('should get all users', async () => {
|
|
1053
|
-
const response = await client.get('/api/users');
|
|
1054
|
-
expect(response.status).toBe(200);
|
|
1055
|
-
expect(Array.isArray(response.json())).toBe(true);
|
|
1056
|
-
});
|
|
1057
|
-
|
|
1058
|
-
it('should create user', async () => {
|
|
1059
|
-
const response = await client.post('/api/users', {
|
|
1060
|
-
body: {
|
|
1061
|
-
name: 'John Doe',
|
|
1062
|
-
email: 'john@example.com'
|
|
1063
|
-
}
|
|
1064
|
-
});
|
|
1065
|
-
expect(response.status).toBe(201);
|
|
1066
|
-
expect(response.json().id).toBeDefined();
|
|
1067
|
-
});
|
|
1068
|
-
|
|
1069
|
-
it('should authenticate user', async () => {
|
|
1070
|
-
const loginResp = await client.post('/api/auth/login', {
|
|
1071
|
-
body: { email: 'john@example.com', password: 'password' }
|
|
1072
|
-
});
|
|
1073
|
-
|
|
1074
|
-
client.authenticate(loginResp.json().token);
|
|
1075
|
-
|
|
1076
|
-
const userResp = await client.get('/api/me');
|
|
1077
|
-
expect(userResp.status).toBe(200);
|
|
1078
|
-
|
|
1079
|
-
client.clearAuth();
|
|
1080
|
-
});
|
|
1081
|
-
});
|
|
1082
|
-
```
|
|
1083
|
-
|
|
1084
|
-
### Example 2: Data Factory
|
|
1085
|
-
|
|
1086
|
-
```typescript
|
|
1087
|
-
import { defineFactory, generators } from '../advanced/testing';
|
|
1088
|
-
|
|
1089
|
-
const userFactory = defineFactory({
|
|
1090
|
-
id: generators.uuid(),
|
|
1091
|
-
email: generators.email(),
|
|
1092
|
-
firstName: generators.firstName(),
|
|
1093
|
-
lastName: generators.lastName(),
|
|
1094
|
-
phone: generators.phone(),
|
|
1095
|
-
country: generators.countryCode(),
|
|
1096
|
-
createdAt: generators.pastDate(365)
|
|
1097
|
-
});
|
|
1098
|
-
|
|
1099
|
-
userFactory.trait('admin', {
|
|
1100
|
-
role: 'admin',
|
|
1101
|
-
permissions: ['read', 'write', 'delete']
|
|
1102
|
-
});
|
|
1103
|
-
|
|
1104
|
-
userFactory.trait('inactive', {
|
|
1105
|
-
active: false
|
|
1106
|
-
});
|
|
1107
|
-
|
|
1108
|
-
// Usage
|
|
1109
|
-
const user = userFactory.build();
|
|
1110
|
-
const admin = userFactory.build({ traits: ['admin'] });
|
|
1111
|
-
const inactiveUsers = userFactory.buildMany(10, { traits: ['inactive'] });
|
|
1112
|
-
```
|
|
1113
|
-
|
|
1114
|
-
### Example 3: Mock Server
|
|
1115
|
-
|
|
1116
|
-
```typescript
|
|
1117
|
-
import { createMockServer } from '../advanced/testing';
|
|
1118
|
-
import axios from 'axios';
|
|
1119
|
-
|
|
1120
|
-
describe('External API Integration', () => {
|
|
1121
|
-
let mockServer;
|
|
1122
|
-
let serverUrl;
|
|
1123
|
-
|
|
1124
|
-
beforeAll(async () => {
|
|
1125
|
-
mockServer = createMockServer();
|
|
1126
|
-
|
|
1127
|
-
mockServer.get('/api/users', (req) => ({
|
|
1128
|
-
status: 200,
|
|
1129
|
-
body: [{ id: 1, name: 'John' }]
|
|
1130
|
-
}));
|
|
1131
|
-
|
|
1132
|
-
mockServer.post('/api/users', (req) => ({
|
|
1133
|
-
status: 201,
|
|
1134
|
-
body: { id: 2, ...req.body }
|
|
1135
|
-
}));
|
|
1136
|
-
|
|
1137
|
-
serverUrl = await mockServer.start();
|
|
1138
|
-
});
|
|
1139
|
-
|
|
1140
|
-
afterAll(async () => {
|
|
1141
|
-
await mockServer.stop();
|
|
1142
|
-
});
|
|
1143
|
-
|
|
1144
|
-
it('should call external API', async () => {
|
|
1145
|
-
const response = await axios.get(`${serverUrl}/api/users`);
|
|
1146
|
-
expect(response.status).toBe(200);
|
|
1147
|
-
expect(response.data).toHaveLength(1);
|
|
1148
|
-
});
|
|
1149
|
-
|
|
1150
|
-
it('should track API calls', () => {
|
|
1151
|
-
expect(mockServer.wasCalled('/api/users', 'GET')).toBe(true);
|
|
1152
|
-
expect(mockServer.callCount('/api/users', 'GET')).toBe(1);
|
|
1153
|
-
});
|
|
1154
|
-
});
|
|
1155
|
-
```
|
|
1156
|
-
|
|
1157
|
-
### Example 4: Load Testing
|
|
1158
|
-
|
|
1159
|
-
```typescript
|
|
1160
|
-
import { createLoadTest, formatLoadTestResults } from '../advanced/testing';
|
|
1161
|
-
|
|
1162
|
-
describe('Load Testing', () => {
|
|
1163
|
-
it('should handle concurrent requests', async () => {
|
|
1164
|
-
const loadTest = createLoadTest({
|
|
1165
|
-
baseUrl: 'http://localhost:3000',
|
|
1166
|
-
vus: 50,
|
|
1167
|
-
duration: '60s',
|
|
1168
|
-
thresholds: {
|
|
1169
|
-
'http_req_duration': ['p(95) < 500', 'p(99) < 1000'],
|
|
1170
|
-
'http_req_failed': ['rate < 0.01']
|
|
1171
|
-
}
|
|
1172
|
-
});
|
|
1173
|
-
|
|
1174
|
-
loadTest.test('default', async (vu, http) => {
|
|
1175
|
-
const response = await http.get('/api/users');
|
|
1176
|
-
if (response.status !== 200) {
|
|
1177
|
-
throw new Error(`Expected 200, got ${response.status}`);
|
|
1178
|
-
}
|
|
1179
|
-
});
|
|
1180
|
-
|
|
1181
|
-
const results = await loadTest.run();
|
|
1182
|
-
|
|
1183
|
-
console.log(formatLoadTestResults(results));
|
|
1184
|
-
expect(results.thresholdsPassed).toBe(true);
|
|
1185
|
-
});
|
|
1186
|
-
});
|
|
1187
|
-
```
|
|
1188
|
-
|
|
1189
|
-
### Example 5: Complete Integration Test
|
|
1190
|
-
|
|
1191
|
-
```typescript
|
|
1192
|
-
import {
|
|
1193
|
-
TestClient,
|
|
1194
|
-
createTestSuite,
|
|
1195
|
-
defineFactory,
|
|
1196
|
-
generators,
|
|
1197
|
-
MockDatabase
|
|
1198
|
-
} from '../advanced/testing';
|
|
1199
|
-
import { app } from './app';
|
|
1200
|
-
|
|
1201
|
-
describe('Complete User Flow', () => {
|
|
1202
|
-
const suite = createTestSuite({
|
|
1203
|
-
app,
|
|
1204
|
-
beforeAll: async () => {
|
|
1205
|
-
await db.connect();
|
|
1206
|
-
},
|
|
1207
|
-
afterAll: async () => {
|
|
1208
|
-
await db.disconnect();
|
|
1209
|
-
},
|
|
1210
|
-
beforeEach: async () => {
|
|
1211
|
-
await db.truncate('users');
|
|
1212
|
-
}
|
|
1213
|
-
});
|
|
1214
|
-
|
|
1215
|
-
const userFactory = defineFactory({
|
|
1216
|
-
id: generators.uuid(),
|
|
1217
|
-
email: generators.email(),
|
|
1218
|
-
password: generators.password(),
|
|
1219
|
-
name: generators.fullName()
|
|
1220
|
-
});
|
|
1221
|
-
|
|
1222
|
-
userFactory.setPersist(async (user) => {
|
|
1223
|
-
return db.users.create(user);
|
|
1224
|
-
});
|
|
1225
|
-
|
|
1226
|
-
it('should complete user lifecycle', async () => {
|
|
1227
|
-
const ctx = await suite.setup();
|
|
1228
|
-
|
|
1229
|
-
// Create user
|
|
1230
|
-
const created = await userFactory.create();
|
|
1231
|
-
expect(created.id).toBeDefined();
|
|
1232
|
-
|
|
1233
|
-
// Get user
|
|
1234
|
-
const response = await ctx.client.get(`/api/users/${created.id}`);
|
|
1235
|
-
expect(response.status).toBe(200);
|
|
1236
|
-
expect(response.json().email).toBe(created.email);
|
|
1237
|
-
|
|
1238
|
-
// Update user
|
|
1239
|
-
const updateResp = await ctx.client.put(`/api/users/${created.id}`, {
|
|
1240
|
-
body: { name: 'Updated Name' }
|
|
1241
|
-
});
|
|
1242
|
-
expect(updateResp.status).toBe(200);
|
|
1243
|
-
|
|
1244
|
-
// Delete user
|
|
1245
|
-
const deleteResp = await ctx.client.delete(`/api/users/${created.id}`);
|
|
1246
|
-
expect(deleteResp.status).toBe(204);
|
|
1247
|
-
|
|
1248
|
-
await suite.teardown();
|
|
1249
|
-
});
|
|
1250
|
-
});
|
|
1251
|
-
```
|
|
1252
|
-
|
|
1253
|
-
---
|
|
1254
|
-
|
|
1255
|
-
## Best Practices
|
|
1256
|
-
|
|
1257
|
-
1. **Use Factories for Complex Data**
|
|
1258
|
-
- Lebih maintainable daripada manual object creation
|
|
1259
|
-
- Traits untuk variations yang berbeda
|
|
1260
|
-
|
|
1261
|
-
2. **Isolate Tests**
|
|
1262
|
-
- Gunakan `beforeEach` untuk clean state
|
|
1263
|
-
- Truncate tables sebelum setiap test
|
|
1264
|
-
|
|
1265
|
-
3. **Mock External Dependencies**
|
|
1266
|
-
- Gunakan MockServer untuk external APIs
|
|
1267
|
-
- MockFetch untuk fetch calls
|
|
1268
|
-
- MockDatabase untuk database operations
|
|
1269
|
-
|
|
1270
|
-
4. **Performance Testing**
|
|
1271
|
-
- Monitor p95 dan p99 latencies
|
|
1272
|
-
- Set realistic thresholds
|
|
1273
|
-
- Test dengan realistic load scenarios
|
|
1274
|
-
|
|
1275
|
-
5. **Error Scenarios**
|
|
1276
|
-
- Test error paths dengan mock servers
|
|
1277
|
-
- Test timeouts dan network errors
|
|
1278
|
-
- Test retry logic dengan MockTimers
|
|
1279
|
-
|
|
1280
|
-
---
|
|
1281
|
-
|
|
1282
|
-
## See Also
|
|
1283
|
-
|
|
1284
|
-
- [01 - Getting Started](./01-getting-started.md)
|
|
1285
|
-
- [05 - Validation](./05-validation.md)
|
|
1286
|
-
- [06 - Error Handling](./06-error-handling.md)
|
|
1287
|
-
- [10 - Examples](./10-examples.md)
|