@vertz/testing 0.1.0 → 0.2.0
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 +739 -0
- package/dist/index.d.ts +101 -14
- package/dist/index.js +52 -14
- package/package.json +6 -3
package/README.md
ADDED
|
@@ -0,0 +1,739 @@
|
|
|
1
|
+
# @vertz/testing
|
|
2
|
+
|
|
3
|
+
Test utilities for Vertz applications. Write fast, isolated tests for your routes, services, and business logic.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- **Node.js** 18+ or **Bun** 1.0+
|
|
8
|
+
- **TypeScript** 5.0+
|
|
9
|
+
- **Dependencies:** `@vertz/core`
|
|
10
|
+
- **Test Framework:** Vitest (recommended) or any other test framework
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install --save-dev @vertz/testing
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
This package provides test helpers that work seamlessly with Vitest (recommended) or any other test framework.
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
### Testing a Route
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { createTestApp } from '@vertz/testing';
|
|
26
|
+
import { UserModule } from './modules/users/user.module';
|
|
27
|
+
import { describe, expect, it } from 'vitest';
|
|
28
|
+
|
|
29
|
+
describe('User routes', () => {
|
|
30
|
+
it('returns list of users', async () => {
|
|
31
|
+
const app = createTestApp().register(UserModule);
|
|
32
|
+
|
|
33
|
+
const res = await app.get('/users');
|
|
34
|
+
|
|
35
|
+
expect(res.ok).toBe(true);
|
|
36
|
+
expect(res.body).toEqual({
|
|
37
|
+
users: expect.arrayContaining([
|
|
38
|
+
expect.objectContaining({ name: expect.any(String) }),
|
|
39
|
+
]),
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('creates a new user', async () => {
|
|
44
|
+
const app = createTestApp().register(UserModule);
|
|
45
|
+
|
|
46
|
+
const res = await app.post('/users', {
|
|
47
|
+
body: { name: 'Jane Doe', email: 'jane@example.com' },
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
expect(res.status).toBe(201);
|
|
51
|
+
expect(res.body).toMatchObject({
|
|
52
|
+
id: expect.any(String),
|
|
53
|
+
name: 'Jane Doe',
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Testing a Service
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { createTestService } from '@vertz/testing';
|
|
63
|
+
import { UserService, DatabaseService } from './services';
|
|
64
|
+
import { describe, expect, it } from 'vitest';
|
|
65
|
+
|
|
66
|
+
describe('UserService', () => {
|
|
67
|
+
it('finds user by ID', async () => {
|
|
68
|
+
const userService = await createTestService(UserService)
|
|
69
|
+
.mock(DatabaseService, {
|
|
70
|
+
query: () => Promise.resolve([{ id: '1', name: 'Jane' }]),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const user = await userService.findById('1');
|
|
74
|
+
|
|
75
|
+
expect(user).toEqual({ id: '1', name: 'Jane' });
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## API Reference
|
|
81
|
+
|
|
82
|
+
### `createTestApp()`
|
|
83
|
+
|
|
84
|
+
Creates a test application builder for integration testing routes and modules.
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { createTestApp } from '@vertz/testing';
|
|
88
|
+
|
|
89
|
+
const app = createTestApp();
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Returns a `TestApp` instance with the following methods:
|
|
93
|
+
|
|
94
|
+
#### `app.register(module, options?)`
|
|
95
|
+
|
|
96
|
+
Register a module with optional configuration.
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
app.register(UserModule);
|
|
100
|
+
app.register(UserModule, { apiKey: 'test-key' });
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Parameters:**
|
|
104
|
+
- `module` — A Vertz module created with `createModule()`
|
|
105
|
+
- `options` — Optional configuration passed to the module
|
|
106
|
+
|
|
107
|
+
**Returns:** `TestApp` (chainable)
|
|
108
|
+
|
|
109
|
+
#### `app.mock(service, implementation)`
|
|
110
|
+
|
|
111
|
+
Mock a service at the application level (applies to all requests).
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
app.mock(DatabaseService, {
|
|
115
|
+
query: () => Promise.resolve([]),
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Parameters:**
|
|
120
|
+
- `service` — A service definition created with `moduleDef.service()`
|
|
121
|
+
- `implementation` — Partial implementation of the service methods
|
|
122
|
+
|
|
123
|
+
**Returns:** `TestApp` (chainable)
|
|
124
|
+
|
|
125
|
+
**Note:** You only need to implement the methods your tests use. Unimplemented methods will throw if called.
|
|
126
|
+
|
|
127
|
+
#### `app.mockMiddleware(middleware, result)`
|
|
128
|
+
|
|
129
|
+
Mock a middleware at the application level.
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
app.mockMiddleware(AuthMiddleware, { user: { id: '1' } });
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Parameters:**
|
|
136
|
+
- `middleware` — A middleware definition created with `createMiddleware()`
|
|
137
|
+
- `result` — The value the middleware should provide
|
|
138
|
+
|
|
139
|
+
**Returns:** `TestApp` (chainable)
|
|
140
|
+
|
|
141
|
+
#### `app.env(vars)`
|
|
142
|
+
|
|
143
|
+
Set environment variables for the test application.
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
app.env({ DATABASE_URL: 'test-db', API_KEY: 'test-key' });
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Parameters:**
|
|
150
|
+
- `vars` — Object with environment variable key-value pairs
|
|
151
|
+
|
|
152
|
+
**Returns:** `TestApp` (chainable)
|
|
153
|
+
|
|
154
|
+
#### HTTP Methods: `app.get()`, `app.post()`, `app.put()`, `app.patch()`, `app.delete()`, `app.head()`
|
|
155
|
+
|
|
156
|
+
Make HTTP requests to your application.
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
const res = await app.get('/users');
|
|
160
|
+
const res = await app.post('/users', { body: { name: 'Jane' } });
|
|
161
|
+
const res = await app.put('/users/1', {
|
|
162
|
+
body: { name: 'Jane Doe' },
|
|
163
|
+
headers: { 'Authorization': 'Bearer token' },
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Parameters:**
|
|
168
|
+
- `path` — Request path (e.g., `/users`, `/users/123`)
|
|
169
|
+
- `options` — Optional request options:
|
|
170
|
+
- `body` — Request body (automatically serialized as JSON)
|
|
171
|
+
- `headers` — Request headers
|
|
172
|
+
|
|
173
|
+
**Returns:** `TestRequestBuilder` (awaitable, see below)
|
|
174
|
+
|
|
175
|
+
### `TestRequestBuilder`
|
|
176
|
+
|
|
177
|
+
Returned by HTTP method calls. It's **awaitable** and also allows per-request mocking.
|
|
178
|
+
|
|
179
|
+
#### Await the Request
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
const res = await app.get('/users');
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Returns a `TestResponse`:
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
interface TestResponse {
|
|
189
|
+
status: number; // HTTP status code
|
|
190
|
+
body: unknown; // Parsed response body (JSON)
|
|
191
|
+
headers: Record<string, string>; // Response headers
|
|
192
|
+
ok: boolean; // true if status 2xx
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
#### Per-Request Mocking
|
|
197
|
+
|
|
198
|
+
Override mocks for a single request:
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
const res = await app.get('/users')
|
|
202
|
+
.mock(DatabaseService, {
|
|
203
|
+
query: () => Promise.resolve([{ id: '1', name: 'Mock User' }]),
|
|
204
|
+
})
|
|
205
|
+
.mockMiddleware(AuthMiddleware, { user: { id: 'test-user' } });
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**Methods:**
|
|
209
|
+
- `mock(service, implementation)` — Mock a service for this request only
|
|
210
|
+
- `mockMiddleware(middleware, result)` — Mock a middleware for this request only
|
|
211
|
+
|
|
212
|
+
**Returns:** `TestRequestBuilder` (chainable and awaitable)
|
|
213
|
+
|
|
214
|
+
### `createTestService(service)`
|
|
215
|
+
|
|
216
|
+
Creates a test builder for isolated service testing.
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
import { createTestService } from '@vertz/testing';
|
|
220
|
+
|
|
221
|
+
const serviceInstance = await createTestService(UserService);
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Parameters:**
|
|
225
|
+
- `service` — A service definition created with `moduleDef.service()`
|
|
226
|
+
|
|
227
|
+
**Returns:** `TestServiceBuilder` (awaitable, see below)
|
|
228
|
+
|
|
229
|
+
### `TestServiceBuilder`
|
|
230
|
+
|
|
231
|
+
Returned by `createTestService()`. It's **awaitable** and allows dependency mocking.
|
|
232
|
+
|
|
233
|
+
#### Await the Service
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
const service = await createTestService(UserService);
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Returns the service methods as an object.
|
|
240
|
+
|
|
241
|
+
#### Mock Dependencies
|
|
242
|
+
|
|
243
|
+
If the service has injected dependencies, you must mock them:
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
const service = await createTestService(UserService)
|
|
247
|
+
.mock(DatabaseService, { query: () => Promise.resolve([]) })
|
|
248
|
+
.mock(CacheService, { get: () => null, set: () => {} });
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**Method:**
|
|
252
|
+
- `mock(dependency, implementation)` — Mock an injected dependency
|
|
253
|
+
|
|
254
|
+
**Returns:** `TestServiceBuilder` (chainable and awaitable)
|
|
255
|
+
|
|
256
|
+
**Error:** If you await a service with unmocked dependencies, it will throw:
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
// ❌ Throws: "Missing mock for injected dependency 'db'"
|
|
260
|
+
const service = await createTestService(UserService);
|
|
261
|
+
|
|
262
|
+
// ✅ Correct
|
|
263
|
+
const service = await createTestService(UserService)
|
|
264
|
+
.mock(DatabaseService, { query: () => [] });
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### `DeepPartial<T>`
|
|
268
|
+
|
|
269
|
+
Type helper for creating partial mocks.
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
import type { DeepPartial } from '@vertz/testing';
|
|
273
|
+
|
|
274
|
+
const mock: DeepPartial<ComplexService> = {
|
|
275
|
+
users: {
|
|
276
|
+
findById: () => Promise.resolve({ id: '1' }),
|
|
277
|
+
// Other methods optional
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Allows you to implement only the methods you need for your test, even for nested objects.
|
|
283
|
+
|
|
284
|
+
## Testing Patterns
|
|
285
|
+
|
|
286
|
+
### Testing with Authentication
|
|
287
|
+
|
|
288
|
+
Mock the auth middleware to simulate authenticated requests:
|
|
289
|
+
|
|
290
|
+
```ts
|
|
291
|
+
it('returns user profile when authenticated', async () => {
|
|
292
|
+
const app = createTestApp()
|
|
293
|
+
.register(UserModule)
|
|
294
|
+
.mockMiddleware(AuthMiddleware, {
|
|
295
|
+
user: { id: 'user-123', role: 'admin' }
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
const res = await app.get('/users/me');
|
|
299
|
+
|
|
300
|
+
expect(res.ok).toBe(true);
|
|
301
|
+
expect(res.body).toMatchObject({ id: 'user-123' });
|
|
302
|
+
});
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Per-request authentication:
|
|
306
|
+
|
|
307
|
+
```ts
|
|
308
|
+
it('requires authentication', async () => {
|
|
309
|
+
const app = createTestApp().register(UserModule);
|
|
310
|
+
|
|
311
|
+
// No auth
|
|
312
|
+
const unauthorized = await app.get('/users/me');
|
|
313
|
+
expect(unauthorized.status).toBe(401);
|
|
314
|
+
|
|
315
|
+
// With auth
|
|
316
|
+
const authorized = await app.get('/users/me')
|
|
317
|
+
.mockMiddleware(AuthMiddleware, { user: { id: '1' } });
|
|
318
|
+
expect(authorized.ok).toBe(true);
|
|
319
|
+
});
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Testing with Database
|
|
323
|
+
|
|
324
|
+
Mock database services to avoid hitting a real database:
|
|
325
|
+
|
|
326
|
+
```ts
|
|
327
|
+
it('creates a user in the database', async () => {
|
|
328
|
+
const mockDb = {
|
|
329
|
+
insert: vi.fn().mockResolvedValue({ id: 'new-id' }),
|
|
330
|
+
query: vi.fn(),
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
const app = createTestApp()
|
|
334
|
+
.register(UserModule)
|
|
335
|
+
.mock(DatabaseService, mockDb);
|
|
336
|
+
|
|
337
|
+
await app.post('/users', { body: { name: 'Jane' } });
|
|
338
|
+
|
|
339
|
+
expect(mockDb.insert).toHaveBeenCalledWith(
|
|
340
|
+
expect.objectContaining({ name: 'Jane' })
|
|
341
|
+
);
|
|
342
|
+
});
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Testing Error Handling
|
|
346
|
+
|
|
347
|
+
```ts
|
|
348
|
+
it('returns 404 for missing user', async () => {
|
|
349
|
+
const app = createTestApp()
|
|
350
|
+
.register(UserModule)
|
|
351
|
+
.mock(DatabaseService, {
|
|
352
|
+
findById: () => null,
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
const res = await app.get('/users/999');
|
|
356
|
+
|
|
357
|
+
expect(res.status).toBe(404);
|
|
358
|
+
expect(res.body).toMatchObject({
|
|
359
|
+
error: 'NotFound',
|
|
360
|
+
message: expect.any(String),
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('returns 400 for invalid input', async () => {
|
|
365
|
+
const app = createTestApp().register(UserModule);
|
|
366
|
+
|
|
367
|
+
const res = await app.post('/users', {
|
|
368
|
+
body: { email: 'not-an-email' }, // Invalid email
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
expect(res.status).toBe(400);
|
|
372
|
+
});
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Testing Services with Dependencies
|
|
376
|
+
|
|
377
|
+
```ts
|
|
378
|
+
it('user service finds user by email', async () => {
|
|
379
|
+
const userService = await createTestService(UserService)
|
|
380
|
+
.mock(DatabaseService, {
|
|
381
|
+
query: (sql: string) => {
|
|
382
|
+
if (sql.includes('email')) {
|
|
383
|
+
return Promise.resolve([{ id: '1', email: 'jane@example.com' }]);
|
|
384
|
+
}
|
|
385
|
+
return Promise.resolve([]);
|
|
386
|
+
},
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
const user = await userService.findByEmail('jane@example.com');
|
|
390
|
+
|
|
391
|
+
expect(user).toMatchObject({ id: '1', email: 'jane@example.com' });
|
|
392
|
+
});
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Testing with Query Parameters
|
|
396
|
+
|
|
397
|
+
```ts
|
|
398
|
+
it('filters users by query params', async () => {
|
|
399
|
+
const app = createTestApp().register(UserModule);
|
|
400
|
+
|
|
401
|
+
const res = await app.get('/users?role=admin&active=true');
|
|
402
|
+
|
|
403
|
+
expect(res.ok).toBe(true);
|
|
404
|
+
expect(res.body.users).toEqual(
|
|
405
|
+
expect.arrayContaining([
|
|
406
|
+
expect.objectContaining({ role: 'admin', active: true }),
|
|
407
|
+
])
|
|
408
|
+
);
|
|
409
|
+
});
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Testing Response Headers
|
|
413
|
+
|
|
414
|
+
```ts
|
|
415
|
+
it('sets cache headers', async () => {
|
|
416
|
+
const app = createTestApp().register(UserModule);
|
|
417
|
+
|
|
418
|
+
const res = await app.get('/users');
|
|
419
|
+
|
|
420
|
+
expect(res.headers['cache-control']).toBe('public, max-age=3600');
|
|
421
|
+
});
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### Testing with Request Headers
|
|
425
|
+
|
|
426
|
+
```ts
|
|
427
|
+
it('accepts custom headers', async () => {
|
|
428
|
+
const app = createTestApp().register(ApiModule);
|
|
429
|
+
|
|
430
|
+
const res = await app.get('/data', {
|
|
431
|
+
headers: {
|
|
432
|
+
'X-API-Key': 'test-key',
|
|
433
|
+
'Accept-Language': 'en-US',
|
|
434
|
+
},
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
expect(res.ok).toBe(true);
|
|
438
|
+
});
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### Testing Schema Validation
|
|
442
|
+
|
|
443
|
+
Vertz automatically validates request/response schemas. Test that validation works:
|
|
444
|
+
|
|
445
|
+
```ts
|
|
446
|
+
it('validates request body against schema', async () => {
|
|
447
|
+
const app = createTestApp().register(UserModule);
|
|
448
|
+
|
|
449
|
+
const res = await app.post('/users', {
|
|
450
|
+
body: { name: '', email: 'invalid' }, // Invalid data
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
expect(res.status).toBe(400);
|
|
454
|
+
expect(res.body).toMatchObject({
|
|
455
|
+
error: 'BadRequest',
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### Testing Route Parameters
|
|
461
|
+
|
|
462
|
+
```ts
|
|
463
|
+
it('handles route parameters', async () => {
|
|
464
|
+
const app = createTestApp().register(UserModule);
|
|
465
|
+
|
|
466
|
+
const res = await app.get('/users/user-123');
|
|
467
|
+
|
|
468
|
+
expect(res.ok).toBe(true);
|
|
469
|
+
expect(res.body.id).toBe('user-123');
|
|
470
|
+
});
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### Testing Multiple Modules
|
|
474
|
+
|
|
475
|
+
```ts
|
|
476
|
+
it('integrates multiple modules', async () => {
|
|
477
|
+
const app = createTestApp()
|
|
478
|
+
.register(UserModule)
|
|
479
|
+
.register(AuthModule)
|
|
480
|
+
.register(PaymentModule);
|
|
481
|
+
|
|
482
|
+
// Test cross-module behavior
|
|
483
|
+
const loginRes = await app.post('/auth/login', {
|
|
484
|
+
body: { email: 'jane@example.com', password: 'secret' },
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
const token = loginRes.body.token;
|
|
488
|
+
|
|
489
|
+
const profileRes = await app.get('/users/me', {
|
|
490
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
expect(profileRes.ok).toBe(true);
|
|
494
|
+
});
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
### Testing with Service State
|
|
498
|
+
|
|
499
|
+
If a service maintains state via `onInit`:
|
|
500
|
+
|
|
501
|
+
```ts
|
|
502
|
+
it('initializes service with state', async () => {
|
|
503
|
+
const service = await createTestService(CounterService)
|
|
504
|
+
.mock(StorageService, {
|
|
505
|
+
load: () => Promise.resolve({ count: 5 }),
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
const count = await service.getCount();
|
|
509
|
+
expect(count).toBe(5);
|
|
510
|
+
|
|
511
|
+
await service.increment();
|
|
512
|
+
const newCount = await service.getCount();
|
|
513
|
+
expect(newCount).toBe(6);
|
|
514
|
+
});
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### Testing Module Options
|
|
518
|
+
|
|
519
|
+
Pass module options to configure behavior:
|
|
520
|
+
|
|
521
|
+
```ts
|
|
522
|
+
it('uses module options', async () => {
|
|
523
|
+
const app = createTestApp().register(UserModule, {
|
|
524
|
+
maxUsers: 100,
|
|
525
|
+
enableCache: false,
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
const res = await app.get('/users');
|
|
529
|
+
expect(res.ok).toBe(true);
|
|
530
|
+
});
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
## Integration with Vitest
|
|
534
|
+
|
|
535
|
+
This package is designed to work seamlessly with Vitest. Add to your `vitest.config.ts`:
|
|
536
|
+
|
|
537
|
+
```ts
|
|
538
|
+
import { defineConfig } from 'vitest/config';
|
|
539
|
+
|
|
540
|
+
export default defineConfig({
|
|
541
|
+
test: {
|
|
542
|
+
globals: true,
|
|
543
|
+
environment: 'node',
|
|
544
|
+
},
|
|
545
|
+
});
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
Then write tests using Vitest's API:
|
|
549
|
+
|
|
550
|
+
```ts
|
|
551
|
+
import { createTestApp } from '@vertz/testing';
|
|
552
|
+
import { describe, expect, it, beforeEach, vi } from 'vitest';
|
|
553
|
+
|
|
554
|
+
describe('User routes', () => {
|
|
555
|
+
let app: ReturnType<typeof createTestApp>;
|
|
556
|
+
|
|
557
|
+
beforeEach(() => {
|
|
558
|
+
app = createTestApp().register(UserModule);
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
it('returns users', async () => {
|
|
562
|
+
const res = await app.get('/users');
|
|
563
|
+
expect(res.ok).toBe(true);
|
|
564
|
+
});
|
|
565
|
+
});
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
### Using Vitest Mocks
|
|
569
|
+
|
|
570
|
+
Combine with `vi.fn()` for advanced mocking:
|
|
571
|
+
|
|
572
|
+
```ts
|
|
573
|
+
it('calls database with correct params', async () => {
|
|
574
|
+
const queryFn = vi.fn().mockResolvedValue([]);
|
|
575
|
+
|
|
576
|
+
const app = createTestApp()
|
|
577
|
+
.register(UserModule)
|
|
578
|
+
.mock(DatabaseService, { query: queryFn });
|
|
579
|
+
|
|
580
|
+
await app.get('/users?role=admin');
|
|
581
|
+
|
|
582
|
+
expect(queryFn).toHaveBeenCalledWith(
|
|
583
|
+
expect.stringContaining('role = $1'),
|
|
584
|
+
['admin']
|
|
585
|
+
);
|
|
586
|
+
});
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
## TypeScript Support
|
|
590
|
+
|
|
591
|
+
All exports are fully typed with strict type inference:
|
|
592
|
+
|
|
593
|
+
```ts
|
|
594
|
+
import type {
|
|
595
|
+
TestApp,
|
|
596
|
+
TestRequestBuilder,
|
|
597
|
+
TestResponse,
|
|
598
|
+
TestServiceBuilder,
|
|
599
|
+
DeepPartial,
|
|
600
|
+
} from '@vertz/testing';
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
The test utilities preserve full type safety:
|
|
604
|
+
|
|
605
|
+
```ts
|
|
606
|
+
// ✅ Type-safe service methods
|
|
607
|
+
const service = await createTestService(UserService);
|
|
608
|
+
const user = await service.findById('1'); // Typed as User
|
|
609
|
+
|
|
610
|
+
// ✅ Type-safe mocks
|
|
611
|
+
app.mock(DatabaseService, {
|
|
612
|
+
query: () => [], // Return type inferred
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
// ❌ TypeScript error: Property 'invalidMethod' does not exist
|
|
616
|
+
app.mock(DatabaseService, {
|
|
617
|
+
invalidMethod: () => {},
|
|
618
|
+
});
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
## Tips & Best Practices
|
|
622
|
+
|
|
623
|
+
### 1. Use Per-Request Mocks for Varying Behavior
|
|
624
|
+
|
|
625
|
+
When you need different behavior per test case, use per-request mocks:
|
|
626
|
+
|
|
627
|
+
```ts
|
|
628
|
+
it('handles different user states', async () => {
|
|
629
|
+
const app = createTestApp().register(UserModule);
|
|
630
|
+
|
|
631
|
+
// Active user
|
|
632
|
+
const activeRes = await app.get('/users/1')
|
|
633
|
+
.mock(DatabaseService, { findById: () => ({ id: '1', active: true }) });
|
|
634
|
+
expect(activeRes.body.active).toBe(true);
|
|
635
|
+
|
|
636
|
+
// Inactive user
|
|
637
|
+
const inactiveRes = await app.get('/users/1')
|
|
638
|
+
.mock(DatabaseService, { findById: () => ({ id: '1', active: false }) });
|
|
639
|
+
expect(inactiveRes.body.active).toBe(false);
|
|
640
|
+
});
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
### 2. Mock Only What You Need
|
|
644
|
+
|
|
645
|
+
You don't need to implement every method — only the ones your test uses:
|
|
646
|
+
|
|
647
|
+
```ts
|
|
648
|
+
// ✅ Minimal mock
|
|
649
|
+
app.mock(DatabaseService, {
|
|
650
|
+
findById: () => ({ id: '1' }),
|
|
651
|
+
// Other methods omitted — they won't be called
|
|
652
|
+
});
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
### 3. Use `beforeEach` for Common Setup
|
|
656
|
+
|
|
657
|
+
```ts
|
|
658
|
+
describe('User API', () => {
|
|
659
|
+
let app: ReturnType<typeof createTestApp>;
|
|
660
|
+
|
|
661
|
+
beforeEach(() => {
|
|
662
|
+
app = createTestApp()
|
|
663
|
+
.register(UserModule)
|
|
664
|
+
.mock(DatabaseService, { /* common mocks */ });
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
it('test 1', async () => { /* ... */ });
|
|
668
|
+
it('test 2', async () => { /* ... */ });
|
|
669
|
+
});
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
### 4. Test Real Error Cases
|
|
673
|
+
|
|
674
|
+
Mock errors to test error handling:
|
|
675
|
+
|
|
676
|
+
```ts
|
|
677
|
+
it('handles database errors', async () => {
|
|
678
|
+
const app = createTestApp()
|
|
679
|
+
.register(UserModule)
|
|
680
|
+
.mock(DatabaseService, {
|
|
681
|
+
query: () => { throw new Error('Connection failed'); },
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
const res = await app.get('/users');
|
|
685
|
+
|
|
686
|
+
expect(res.status).toBe(500);
|
|
687
|
+
});
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
### 5. Test Response Schemas
|
|
691
|
+
|
|
692
|
+
If you define response schemas, Vertz validates them automatically:
|
|
693
|
+
|
|
694
|
+
```ts
|
|
695
|
+
it('response matches schema', async () => {
|
|
696
|
+
const app = createTestApp().register(UserModule);
|
|
697
|
+
|
|
698
|
+
const res = await app.get('/users/1');
|
|
699
|
+
|
|
700
|
+
// If the handler returns data that doesn't match the schema,
|
|
701
|
+
// the test will throw a ResponseValidationError
|
|
702
|
+
expect(res.ok).toBe(true);
|
|
703
|
+
});
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
## Common Errors
|
|
707
|
+
|
|
708
|
+
### Missing Mock for Dependency
|
|
709
|
+
|
|
710
|
+
```
|
|
711
|
+
Error: Missing mock for injected dependency "db".
|
|
712
|
+
Call .mock(dbService, impl) before awaiting.
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
**Solution:** Mock all injected dependencies:
|
|
716
|
+
|
|
717
|
+
```ts
|
|
718
|
+
const service = await createTestService(UserService)
|
|
719
|
+
.mock(DatabaseService, { query: () => [] });
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
### Response Validation Error
|
|
723
|
+
|
|
724
|
+
```
|
|
725
|
+
ResponseValidationError: Response validation failed: Invalid type
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
**Cause:** The route handler returned data that doesn't match the response schema.
|
|
729
|
+
|
|
730
|
+
**Solution:** Fix the handler or the schema to match expected output.
|
|
731
|
+
|
|
732
|
+
## Related Packages
|
|
733
|
+
|
|
734
|
+
- [@vertz/core](../core) — Core framework and module system
|
|
735
|
+
- [@vertz/schema](../schema) — Schema validation (used in request/response validation)
|
|
736
|
+
|
|
737
|
+
## License
|
|
738
|
+
|
|
739
|
+
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -1,26 +1,45 @@
|
|
|
1
|
-
import { NamedMiddlewareDef, NamedModule, NamedServiceDef } from "@vertz/
|
|
1
|
+
import { NamedMiddlewareDef, NamedModule, NamedServiceDef } from "@vertz/server";
|
|
2
2
|
type DeepPartial<T> = { [P in keyof T]? : T[P] extends object ? DeepPartial<T[P]> : T[P] };
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Route map entry shape that each route in AppRouteMap must follow.
|
|
5
|
+
*/
|
|
6
|
+
interface RouteMapEntry {
|
|
7
|
+
params: Record<string, string>;
|
|
8
|
+
query: Record<string, string>;
|
|
5
9
|
body: unknown;
|
|
6
10
|
headers: Record<string, string>;
|
|
11
|
+
response: unknown;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* TestResponse with typed body based on route's response type.
|
|
15
|
+
*/
|
|
16
|
+
interface TestResponse<TResponse = unknown> {
|
|
17
|
+
status: number;
|
|
18
|
+
body: TResponse;
|
|
19
|
+
headers: Record<string, string>;
|
|
7
20
|
ok: boolean;
|
|
8
21
|
}
|
|
9
|
-
interface TestRequestBuilder extends PromiseLike<TestResponse
|
|
22
|
+
interface TestRequestBuilder<TResponse = unknown> extends PromiseLike<TestResponse<TResponse>> {
|
|
10
23
|
mock<
|
|
11
24
|
TDeps,
|
|
12
25
|
TState,
|
|
13
26
|
TMethods
|
|
14
|
-
>(service: NamedServiceDef<TDeps, TState, TMethods>, impl: DeepPartial<TMethods>): TestRequestBuilder
|
|
27
|
+
>(service: NamedServiceDef<TDeps, TState, TMethods>, impl: DeepPartial<TMethods>): TestRequestBuilder<TResponse>;
|
|
15
28
|
mockMiddleware<
|
|
16
29
|
TReq extends Record<string, unknown>,
|
|
17
30
|
TProv extends Record<string, unknown>
|
|
18
|
-
>(middleware: NamedMiddlewareDef<TReq, TProv>, result: TProv): TestRequestBuilder
|
|
31
|
+
>(middleware: NamedMiddlewareDef<TReq, TProv>, result: TProv): TestRequestBuilder<TResponse>;
|
|
19
32
|
}
|
|
20
|
-
|
|
21
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Input options for a request, with typed body from the route map.
|
|
35
|
+
*/
|
|
36
|
+
interface RequestOptions<TBody = unknown> {
|
|
37
|
+
body?: TBody;
|
|
22
38
|
headers?: Record<string, string>;
|
|
23
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Untyped test app interface for backwards compatibility.
|
|
42
|
+
*/
|
|
24
43
|
interface TestApp {
|
|
25
44
|
register(module: NamedModule, options?: Record<string, unknown>): TestApp;
|
|
26
45
|
mock<
|
|
@@ -40,18 +59,86 @@ interface TestApp {
|
|
|
40
59
|
delete(path: string, options?: RequestOptions): TestRequestBuilder;
|
|
41
60
|
head(path: string, options?: RequestOptions): TestRequestBuilder;
|
|
42
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Typed test app interface with route map for type-safe requests.
|
|
64
|
+
* @template TRouteMap - Route map interface mapping route keys (e.g., 'GET /users') to their types.
|
|
65
|
+
*/
|
|
66
|
+
interface TestAppWithRoutes<TRouteMap extends RouteMapEntry> {
|
|
67
|
+
register(module: NamedModule, options?: Record<string, unknown>): TestAppWithRoutes<TRouteMap>;
|
|
68
|
+
mock<
|
|
69
|
+
TDeps,
|
|
70
|
+
TState,
|
|
71
|
+
TMethods
|
|
72
|
+
>(service: NamedServiceDef<TDeps, TState, TMethods>, impl: DeepPartial<TMethods>): TestAppWithRoutes<TRouteMap>;
|
|
73
|
+
mockMiddleware<
|
|
74
|
+
TReq extends Record<string, unknown>,
|
|
75
|
+
TProv extends Record<string, unknown>
|
|
76
|
+
>(middleware: NamedMiddlewareDef<TReq, TProv>, result: TProv): TestAppWithRoutes<TRouteMap>;
|
|
77
|
+
env(vars: Record<string, unknown>): TestAppWithRoutes<TRouteMap>;
|
|
78
|
+
get<TKey extends `GET ${string}` & keyof TRouteMap>(path: string, options?: RequestOptions<TRouteMap[TKey] extends {
|
|
79
|
+
body: infer TBody;
|
|
80
|
+
} ? TBody : never>): TestRequestBuilder<TRouteMap[TKey] extends {
|
|
81
|
+
response: infer TResponse;
|
|
82
|
+
} ? TResponse : unknown>;
|
|
83
|
+
post<TKey extends `POST ${string}` & keyof TRouteMap>(path: string, options?: RequestOptions<TRouteMap[TKey] extends {
|
|
84
|
+
body: infer TBody;
|
|
85
|
+
} ? TBody : never>): TestRequestBuilder<TRouteMap[TKey] extends {
|
|
86
|
+
response: infer TResponse;
|
|
87
|
+
} ? TResponse : unknown>;
|
|
88
|
+
put<TKey extends `PUT ${string}` & keyof TRouteMap>(path: string, options?: RequestOptions<TRouteMap[TKey] extends {
|
|
89
|
+
body: infer TBody;
|
|
90
|
+
} ? TBody : never>): TestRequestBuilder<TRouteMap[TKey] extends {
|
|
91
|
+
response: infer TResponse;
|
|
92
|
+
} ? TResponse : unknown>;
|
|
93
|
+
patch<TKey extends `PATCH ${string}` & keyof TRouteMap>(path: string, options?: RequestOptions<TRouteMap[TKey] extends {
|
|
94
|
+
body: infer TBody;
|
|
95
|
+
} ? TBody : never>): TestRequestBuilder<TRouteMap[TKey] extends {
|
|
96
|
+
response: infer TResponse;
|
|
97
|
+
} ? TResponse : unknown>;
|
|
98
|
+
delete<TKey extends `DELETE ${string}` & keyof TRouteMap>(path: string, options?: RequestOptions<TRouteMap[TKey] extends {
|
|
99
|
+
body: infer TBody;
|
|
100
|
+
} ? TBody : never>): TestRequestBuilder<TRouteMap[TKey] extends {
|
|
101
|
+
response: infer TResponse;
|
|
102
|
+
} ? TResponse : unknown>;
|
|
103
|
+
head<TKey extends `HEAD ${string}` & keyof TRouteMap>(path: string, options?: RequestOptions<TRouteMap[TKey] extends {
|
|
104
|
+
body: infer TBody;
|
|
105
|
+
} ? TBody : never>): TestRequestBuilder<TRouteMap[TKey] extends {
|
|
106
|
+
response: infer TResponse;
|
|
107
|
+
} ? TResponse : unknown>;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Creates a test app for making HTTP requests against registered modules.
|
|
111
|
+
* Returns an untyped app for backwards compatibility.
|
|
112
|
+
* @returns A test app instance for registering modules and making requests.
|
|
113
|
+
*/
|
|
43
114
|
declare function createTestApp(): TestApp;
|
|
115
|
+
/**
|
|
116
|
+
* Creates a typed test app with route map for type-safe requests.
|
|
117
|
+
* @template TRouteMap - Route map interface mapping route keys to their types.
|
|
118
|
+
* @returns A typed test app instance.
|
|
119
|
+
*/
|
|
120
|
+
declare function createTestApp<TRouteMap extends RouteMapEntry>(): TestAppWithRoutes<TRouteMap>;
|
|
44
121
|
import { NamedServiceDef as NamedServiceDef2 } from "@vertz/core";
|
|
45
|
-
interface TestServiceBuilder<
|
|
122
|
+
interface TestServiceBuilder<
|
|
123
|
+
TDeps,
|
|
124
|
+
TState,
|
|
125
|
+
TMethods,
|
|
126
|
+
TOptions extends Record<string, unknown> = Record<string, unknown>,
|
|
127
|
+
TEnv extends Record<string, unknown> = Record<string, unknown>
|
|
128
|
+
> extends PromiseLike<TMethods> {
|
|
46
129
|
mock<
|
|
47
130
|
TDep,
|
|
48
|
-
|
|
131
|
+
TDepState,
|
|
49
132
|
TMock
|
|
50
|
-
>(service: NamedServiceDef2<TDep,
|
|
133
|
+
>(service: NamedServiceDef2<TDep, TDepState, TMock>, impl: DeepPartial<TMock>): TestServiceBuilder<TDeps, TState, TMethods, TOptions, TEnv>;
|
|
134
|
+
options(opts: Partial<TOptions>): TestServiceBuilder<TDeps, TState, TMethods, TOptions, TEnv>;
|
|
135
|
+
env(env: Partial<TEnv>): TestServiceBuilder<TDeps, TState, TMethods, TOptions, TEnv>;
|
|
51
136
|
}
|
|
52
137
|
declare function createTestService<
|
|
53
138
|
TDeps,
|
|
54
139
|
TState,
|
|
55
|
-
TMethods
|
|
56
|
-
|
|
57
|
-
|
|
140
|
+
TMethods,
|
|
141
|
+
TOptions extends Record<string, unknown> = Record<string, unknown>,
|
|
142
|
+
TEnv extends Record<string, unknown> = Record<string, unknown>
|
|
143
|
+
>(serviceDef: NamedServiceDef2<TDeps, TState, TMethods, TOptions, TEnv>): TestServiceBuilder<TDeps, TState, TMethods, TOptions, TEnv>;
|
|
144
|
+
export { createTestService, createTestApp, TestServiceBuilder, TestResponse, TestRequestBuilder, TestApp, RouteMapEntry, RequestOptions, DeepPartial };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
// src/test-app.ts
|
|
2
|
-
import {
|
|
3
|
-
BadRequestException
|
|
4
|
-
} from "@vertz/core";
|
|
5
2
|
import {
|
|
6
3
|
buildCtx,
|
|
7
4
|
createErrorResponse,
|
|
@@ -11,13 +8,9 @@ import {
|
|
|
11
8
|
runMiddlewareChain,
|
|
12
9
|
Trie
|
|
13
10
|
} from "@vertz/core/internals";
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
super(`Response validation failed: ${message}`);
|
|
18
|
-
this.name = "ResponseValidationError";
|
|
19
|
-
}
|
|
20
|
-
}
|
|
11
|
+
import {
|
|
12
|
+
BadRequestException
|
|
13
|
+
} from "@vertz/server";
|
|
21
14
|
function validateSchema(schema, value, label) {
|
|
22
15
|
try {
|
|
23
16
|
return schema.parse(value);
|
|
@@ -37,10 +30,20 @@ function createTestApp() {
|
|
|
37
30
|
function buildHandler(perRequest) {
|
|
38
31
|
const trie = new Trie;
|
|
39
32
|
const realServices = new Map;
|
|
40
|
-
for (const { module } of registrations) {
|
|
33
|
+
for (const { module, options } of registrations) {
|
|
41
34
|
for (const service of module.services) {
|
|
42
35
|
if (!realServices.has(service)) {
|
|
43
|
-
|
|
36
|
+
let parsedOptions = {};
|
|
37
|
+
if (service.options && options) {
|
|
38
|
+
const parsed = service.options.safeParse(options);
|
|
39
|
+
if (parsed.success) {
|
|
40
|
+
parsedOptions = parsed.data;
|
|
41
|
+
} else {
|
|
42
|
+
throw new Error(`Invalid options for service ${service.moduleName}: ${parsed.error.issues.map((i) => i.message).join(", ")}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const env = {};
|
|
46
|
+
realServices.set(service, service.methods({}, undefined, parsedOptions, env));
|
|
44
47
|
}
|
|
45
48
|
}
|
|
46
49
|
}
|
|
@@ -197,9 +200,18 @@ function createTestApp() {
|
|
|
197
200
|
};
|
|
198
201
|
return app;
|
|
199
202
|
}
|
|
203
|
+
|
|
204
|
+
class ResponseValidationError extends Error {
|
|
205
|
+
constructor(message) {
|
|
206
|
+
super(`Response validation failed: ${message}`);
|
|
207
|
+
this.name = "ResponseValidationError";
|
|
208
|
+
}
|
|
209
|
+
}
|
|
200
210
|
// src/test-service.ts
|
|
201
211
|
function createTestService(serviceDef) {
|
|
202
212
|
const serviceMocks = new Map;
|
|
213
|
+
let providedOptions = {};
|
|
214
|
+
let providedEnv = {};
|
|
203
215
|
async function resolve() {
|
|
204
216
|
const deps = {};
|
|
205
217
|
if (serviceDef.inject) {
|
|
@@ -211,14 +223,40 @@ function createTestService(serviceDef) {
|
|
|
211
223
|
deps[name] = mock;
|
|
212
224
|
}
|
|
213
225
|
}
|
|
214
|
-
|
|
215
|
-
|
|
226
|
+
let options = {};
|
|
227
|
+
if (serviceDef.options) {
|
|
228
|
+
const parsed = serviceDef.options.safeParse(providedOptions);
|
|
229
|
+
if (parsed.success) {
|
|
230
|
+
options = parsed.data;
|
|
231
|
+
} else {
|
|
232
|
+
throw new Error(`Invalid options: ${parsed.error.issues.map((i) => i.message).join(", ")}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
let env = {};
|
|
236
|
+
if (serviceDef.env) {
|
|
237
|
+
const parsed = serviceDef.env.safeParse(providedEnv);
|
|
238
|
+
if (parsed.success) {
|
|
239
|
+
env = parsed.data;
|
|
240
|
+
} else {
|
|
241
|
+
throw new Error(`Invalid env: ${parsed.error.issues.map((i) => i.message).join(", ")}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
const state = serviceDef.onInit ? await serviceDef.onInit(deps, options, env) : undefined;
|
|
245
|
+
return serviceDef.methods(deps, state, options, env);
|
|
216
246
|
}
|
|
217
247
|
const builder = {
|
|
218
248
|
mock(service, impl) {
|
|
219
249
|
serviceMocks.set(service, impl);
|
|
220
250
|
return builder;
|
|
221
251
|
},
|
|
252
|
+
options(opts) {
|
|
253
|
+
providedOptions = opts;
|
|
254
|
+
return builder;
|
|
255
|
+
},
|
|
256
|
+
env(envVars) {
|
|
257
|
+
providedEnv = envVars;
|
|
258
|
+
return builder;
|
|
259
|
+
},
|
|
222
260
|
then(onfulfilled, onrejected) {
|
|
223
261
|
return resolve().then(onfulfilled, onrejected);
|
|
224
262
|
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vertz/testing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"description": "Testing utilities for Vertz applications",
|
|
6
7
|
"repository": {
|
|
7
8
|
"type": "git",
|
|
8
9
|
"url": "https://github.com/vertz-dev/vertz.git",
|
|
@@ -30,14 +31,16 @@
|
|
|
30
31
|
"typecheck": "tsc --noEmit"
|
|
31
32
|
},
|
|
32
33
|
"dependencies": {
|
|
33
|
-
"@vertz/core": "workspace:*"
|
|
34
|
+
"@vertz/core": "workspace:*",
|
|
35
|
+
"@vertz/server": "workspace:*"
|
|
34
36
|
},
|
|
35
37
|
"devDependencies": {
|
|
36
38
|
"@types/node": "^25.2.1",
|
|
37
39
|
"@vertz/schema": "workspace:*",
|
|
40
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
38
41
|
"bunup": "latest",
|
|
39
42
|
"typescript": "^5.7.0",
|
|
40
|
-
"vitest": "^
|
|
43
|
+
"vitest": "^4.0.18"
|
|
41
44
|
},
|
|
42
45
|
"engines": {
|
|
43
46
|
"node": ">=22"
|