@jterrazz/test 5.3.1 → 6.0.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 +66 -56
- package/dist/index.cjs +562 -455
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +341 -246
- package/dist/index.d.ts +341 -246
- package/dist/index.js +556 -430
- package/dist/index.js.map +1 -1
- package/dist/mock-of.cjs +59 -0
- package/dist/mock-of.cjs.map +1 -0
- package/dist/mock-of.d.cts +27 -0
- package/dist/mock-of.d.ts +27 -0
- package/dist/mock-of.js +19 -0
- package/dist/mock-of.js.map +1 -0
- package/dist/mock.cjs +4 -0
- package/dist/mock.d.cts +2 -0
- package/dist/mock.d.ts +2 -0
- package/dist/mock.js +2 -0
- package/dist/redis.adapter.cjs +214 -0
- package/dist/redis.adapter.cjs.map +1 -0
- package/dist/redis.adapter.d.cts +155 -0
- package/dist/redis.adapter.d.ts +155 -0
- package/dist/redis.adapter.js +202 -0
- package/dist/redis.adapter.js.map +1 -0
- package/dist/services.cjs +4 -0
- package/dist/services.d.cts +2 -0
- package/dist/services.d.ts +2 -0
- package/dist/services.js +2 -0
- package/package.json +14 -8
package/README.md
CHANGED
|
@@ -13,27 +13,30 @@ npm install -D @jterrazz/test vitest
|
|
|
13
13
|
```typescript
|
|
14
14
|
// tests/setup/integration.specification.ts
|
|
15
15
|
import { afterAll } from 'vitest';
|
|
16
|
-
import {
|
|
16
|
+
import { spec, app } from '@jterrazz/test';
|
|
17
|
+
import { postgres } from '@jterrazz/test/services';
|
|
17
18
|
import { createApp } from '../../src/app.js';
|
|
18
19
|
|
|
19
20
|
const db = postgres({ compose: 'db' });
|
|
20
21
|
|
|
21
|
-
export const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
export const run = await spec(
|
|
23
|
+
app(() => createApp({ databaseUrl: db.connectionString })),
|
|
24
|
+
{
|
|
25
|
+
services: [db],
|
|
26
|
+
root: '../../',
|
|
27
|
+
},
|
|
28
|
+
);
|
|
26
29
|
|
|
27
|
-
afterAll(() =>
|
|
30
|
+
afterAll(() => run.cleanup());
|
|
28
31
|
```
|
|
29
32
|
|
|
30
33
|
```typescript
|
|
31
34
|
// tests/e2e/users/users.e2e.test.ts
|
|
32
|
-
import {
|
|
35
|
+
import { run } from '../../setup/integration.specification.js';
|
|
33
36
|
|
|
34
37
|
test('creates a user', async () => {
|
|
35
38
|
// Given — one existing user
|
|
36
|
-
const result = await
|
|
39
|
+
const result = await run('creates user')
|
|
37
40
|
.seed('initial-users.sql')
|
|
38
41
|
.post('/users', 'new-user.json')
|
|
39
42
|
.run();
|
|
@@ -52,21 +55,20 @@ test('creates a user', async () => {
|
|
|
52
55
|
```typescript
|
|
53
56
|
// tests/setup/cli.specification.ts
|
|
54
57
|
import { resolve } from 'node:path';
|
|
55
|
-
import {
|
|
58
|
+
import { spec, command } from '@jterrazz/test';
|
|
56
59
|
|
|
57
|
-
export const
|
|
58
|
-
command: resolve(import.meta.dirname, '../../bin/my-cli.sh'),
|
|
60
|
+
export const run = await spec(command(resolve(import.meta.dirname, '../../bin/my-cli.sh')), {
|
|
59
61
|
root: '../fixtures',
|
|
60
62
|
});
|
|
61
63
|
```
|
|
62
64
|
|
|
63
65
|
```typescript
|
|
64
66
|
// tests/e2e/build/build.e2e.test.ts
|
|
65
|
-
import {
|
|
67
|
+
import { run } from '../../setup/cli.specification.js';
|
|
66
68
|
|
|
67
69
|
test('builds the project', async () => {
|
|
68
70
|
// Given — sample app project
|
|
69
|
-
const result = await
|
|
71
|
+
const result = await run('build').project('sample-app').exec('build').run();
|
|
70
72
|
|
|
71
73
|
// Then — ESM output with source maps
|
|
72
74
|
expect(result.exitCode).toBe(0);
|
|
@@ -81,50 +83,52 @@ test('builds the project', async () => {
|
|
|
81
83
|
|
|
82
84
|
Three modes, same builder API. Each handles infrastructure and cleanup automatically.
|
|
83
85
|
|
|
84
|
-
### `
|
|
86
|
+
### `spec(app(...))` — testcontainers + in-process app
|
|
85
87
|
|
|
86
88
|
Starts real containers via testcontainers. App runs in-process (Hono). Fastest feedback loop.
|
|
87
89
|
|
|
88
90
|
```typescript
|
|
89
|
-
import {
|
|
91
|
+
import { spec, app } from '@jterrazz/test';
|
|
92
|
+
import { postgres, redis } from '@jterrazz/test/services';
|
|
90
93
|
|
|
91
94
|
const db = postgres({ compose: 'db' });
|
|
92
95
|
const cache = redis({ compose: 'cache' });
|
|
93
96
|
|
|
94
|
-
export const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
export const run = await spec(
|
|
98
|
+
app(() => createApp({ databaseUrl: db.connectionString })),
|
|
99
|
+
{
|
|
100
|
+
services: [db, cache],
|
|
101
|
+
root: '../../',
|
|
102
|
+
},
|
|
103
|
+
);
|
|
99
104
|
```
|
|
100
105
|
|
|
101
|
-
### `
|
|
106
|
+
### `spec(stack(...))` — docker compose up + real HTTP
|
|
102
107
|
|
|
103
108
|
Starts the full `docker/compose.test.yaml` stack. App URL and databases auto-detected.
|
|
104
109
|
|
|
105
110
|
```typescript
|
|
106
|
-
import {
|
|
111
|
+
import { spec, stack } from '@jterrazz/test';
|
|
107
112
|
|
|
108
|
-
export const
|
|
109
|
-
root: '../../',
|
|
110
|
-
});
|
|
113
|
+
export const run = await spec(stack('../../'));
|
|
111
114
|
```
|
|
112
115
|
|
|
113
|
-
### `
|
|
116
|
+
### `spec(command(...))` — local command execution
|
|
114
117
|
|
|
115
118
|
Runs CLI commands against fixture projects in temp directories. Optionally starts infrastructure.
|
|
116
119
|
|
|
117
120
|
```typescript
|
|
118
|
-
import {
|
|
121
|
+
import { spec, command } from '@jterrazz/test';
|
|
122
|
+
import { postgres } from '@jterrazz/test/services';
|
|
119
123
|
|
|
120
|
-
export const
|
|
121
|
-
command: resolve(import.meta.dirname, '../../bin/my-cli.sh'),
|
|
124
|
+
export const run = await spec(command(resolve(import.meta.dirname, '../../bin/my-cli.sh')), {
|
|
122
125
|
root: '../fixtures',
|
|
123
126
|
});
|
|
124
127
|
|
|
125
128
|
// With infrastructure (CLI that needs a database)
|
|
126
|
-
|
|
127
|
-
|
|
129
|
+
const db = postgres({ compose: 'db' });
|
|
130
|
+
|
|
131
|
+
export const run = await spec(command('my-migrate-tool'), {
|
|
128
132
|
root: '../fixtures',
|
|
129
133
|
services: [db],
|
|
130
134
|
});
|
|
@@ -132,7 +136,7 @@ export const spec = await cli({
|
|
|
132
136
|
|
|
133
137
|
## Builder API
|
|
134
138
|
|
|
135
|
-
Every test follows the same pattern: `
|
|
139
|
+
Every test follows the same pattern: `run("label") -> setup -> action -> assertions`.
|
|
136
140
|
|
|
137
141
|
### Setup (cross-mode)
|
|
138
142
|
|
|
@@ -142,7 +146,6 @@ Every test follows the same pattern: `spec("label") → setup → action → ass
|
|
|
142
146
|
| `.seed("file.sql", { service: "name" })` | Load SQL into a specific database |
|
|
143
147
|
| `.fixture("file")` | Copy `fixtures/file` into the CLI working directory |
|
|
144
148
|
| `.project("name")` | Copy `fixtures/name/` into a fresh temp dir and run there |
|
|
145
|
-
| `.mock("file.json")` | Register mocked external API response (MSW, planned) |
|
|
146
149
|
|
|
147
150
|
### Actions (one per spec, mutually exclusive)
|
|
148
151
|
|
|
@@ -164,11 +167,11 @@ Every test follows the same pattern: `spec("label") → setup → action → ass
|
|
|
164
167
|
| `.spawn("args", { waitFor, timeout })` | Run long-lived process, resolve on pattern match or timeout |
|
|
165
168
|
| `.env({ KEY: "value" })` | Set env vars on the child process (`null` unsets, `$WORKDIR` expands to the temp cwd) |
|
|
166
169
|
|
|
167
|
-
Every CLI spec runs in a **fresh, empty temp directory** by default. `.project("name")` starts from a copy of `fixtures/name/`; `.fixture("file")` seeds specific files into the temp dir.
|
|
170
|
+
Every CLI spec runs in a **fresh, empty temp directory** by default. `.project("name")` starts from a copy of `fixtures/name/`; `.fixture("file")` seeds specific files into the temp dir.
|
|
168
171
|
|
|
169
172
|
### Assertions
|
|
170
173
|
|
|
171
|
-
Result properties are raw values
|
|
174
|
+
Result properties are raw values -- use vitest `expect()` for assertions. Database and response file assertions use custom async methods.
|
|
172
175
|
|
|
173
176
|
**Raw values (vitest expect):**
|
|
174
177
|
|
|
@@ -195,27 +198,25 @@ Result properties are raw values — use vitest `expect()` for assertions. Datab
|
|
|
195
198
|
| `await result.directory().toMatchFixture("scaffold", { ignore })` | Pass extra ignore patterns; defaults already skip `.git`, `node_modules`, etc. |
|
|
196
199
|
| `await result.directory("out").files()` | List all files (recursive, sorted) for ad-hoc assertions |
|
|
197
200
|
|
|
198
|
-
Run with `JTERRAZZ_TEST_UPDATE=1` (or vitest `-u`) to overwrite fixtures with the current output.
|
|
201
|
+
Run with `JTERRAZZ_TEST_UPDATE=1` (or vitest `-u`) to overwrite fixtures with the current output.
|
|
199
202
|
|
|
200
203
|
**Grep (scoped text matching):**
|
|
201
204
|
|
|
202
205
|
```typescript
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
expect(grep(result.stdout, 'unused-var.ts')).toContain('no-unused-vars');
|
|
206
|
-
expect(grep(result.stdout, 'valid/sorted.ts')).not.toContain('sort-imports');
|
|
206
|
+
expect(result.grep('unused-var.ts')).toContain('no-unused-vars');
|
|
207
|
+
expect(result.grep('valid/sorted.ts')).not.toContain('sort-imports');
|
|
207
208
|
```
|
|
208
209
|
|
|
209
|
-
`grep(
|
|
210
|
+
`result.grep(pattern)` filters multi-line output to the block matching `pattern`, returning a string for vitest assertions.
|
|
210
211
|
|
|
211
212
|
**Response (HTTP body):**
|
|
212
213
|
|
|
213
|
-
| Expression | Description
|
|
214
|
-
| ----------------------------------------------- |
|
|
215
|
-
| `result.response.toMatchFile("expected.json")` | Custom
|
|
216
|
-
| `expect(result.response.body).toEqual({ ... })` | Raw body object for vitest assertions
|
|
214
|
+
| Expression | Description |
|
|
215
|
+
| ----------------------------------------------- | ---------------------------------------------------------------------------- |
|
|
216
|
+
| `result.response.toMatchFile("expected.json")` | Custom -- compares body to `responses/expected.json`, shows diff on mismatch |
|
|
217
|
+
| `expect(result.response.body).toEqual({ ... })` | Raw body object for vitest assertions |
|
|
217
218
|
|
|
218
|
-
**Tables (custom async
|
|
219
|
+
**Tables (custom async -- database queries):**
|
|
219
220
|
|
|
220
221
|
| Expression | Description |
|
|
221
222
|
| ------------------------------------------------------------------------------- | ------------------------------ |
|
|
@@ -223,20 +224,29 @@ expect(grep(result.stdout, 'valid/sorted.ts')).not.toContain('sort-imports');
|
|
|
223
224
|
| `await result.table("events", { service: "analytics-db" }).toMatch({ ... })` | Assert on a specific database |
|
|
224
225
|
| `await result.table("users").toBeEmpty()` | Assert database table is empty |
|
|
225
226
|
|
|
227
|
+
**Docker (container assertions):**
|
|
228
|
+
|
|
229
|
+
| Expression | Description |
|
|
230
|
+
| ------------------- | ------------------------------- |
|
|
231
|
+
| `runner.docker(id)` | Access a docker container by id |
|
|
232
|
+
|
|
226
233
|
## Multi-database support
|
|
227
234
|
|
|
228
235
|
When multiple databases are declared, `seed()` and `result.table()` accept `{ service: "name" }` to target a specific database by its compose name. Without `service`, both default to the first postgres.
|
|
229
236
|
|
|
230
237
|
```typescript
|
|
238
|
+
import { spec, app } from '@jterrazz/test';
|
|
239
|
+
import { postgres } from '@jterrazz/test/services';
|
|
240
|
+
|
|
231
241
|
const db = postgres({ compose: "db" });
|
|
232
242
|
const analyticsDb = postgres({ compose: "analytics-db" });
|
|
233
243
|
|
|
234
|
-
const
|
|
244
|
+
const run = await spec(app(() => createApp({ ... })), {
|
|
235
245
|
services: [db, analyticsDb],
|
|
236
|
-
|
|
246
|
+
root: '../../',
|
|
237
247
|
});
|
|
238
248
|
|
|
239
|
-
const result = await
|
|
249
|
+
const result = await run("cross-db")
|
|
240
250
|
.seed("users.sql")
|
|
241
251
|
.seed("events.sql", { service: "analytics-db" })
|
|
242
252
|
.post("/users", "request.json")
|
|
@@ -253,7 +263,7 @@ await result.table("events", { service: "analytics-db" }).toMatch({
|
|
|
253
263
|
## Service factories
|
|
254
264
|
|
|
255
265
|
```typescript
|
|
256
|
-
import { postgres, redis } from '@jterrazz/test';
|
|
266
|
+
import { postgres, redis } from '@jterrazz/test/services';
|
|
257
267
|
|
|
258
268
|
const db = postgres({ compose: 'db' });
|
|
259
269
|
const cache = redis({ compose: 'cache' });
|
|
@@ -269,7 +279,7 @@ Service handles read image and environment from `docker/compose.test.yaml`. Afte
|
|
|
269
279
|
## Mocking utilities
|
|
270
280
|
|
|
271
281
|
```typescript
|
|
272
|
-
import { mockOf, mockOfDate } from '@jterrazz/test';
|
|
282
|
+
import { mockOf, mockOfDate } from '@jterrazz/test/mock';
|
|
273
283
|
```
|
|
274
284
|
|
|
275
285
|
| Export | Description |
|
|
@@ -322,7 +332,7 @@ Every test uses `// Given` and `// Then` comments. Always both, never one withou
|
|
|
322
332
|
```typescript
|
|
323
333
|
test('creates a user and returns 201', async () => {
|
|
324
334
|
// Given — two existing users
|
|
325
|
-
const result = await
|
|
335
|
+
const result = await run('creates user')
|
|
326
336
|
.seed('initial-users.sql')
|
|
327
337
|
.post('/users', 'new-user.json')
|
|
328
338
|
.run();
|
|
@@ -340,6 +350,6 @@ test('creates a user and returns 201', async () => {
|
|
|
340
350
|
|
|
341
351
|
## Requirements
|
|
342
352
|
|
|
343
|
-
- **Docker**
|
|
344
|
-
- **vitest**
|
|
345
|
-
- **hono**
|
|
353
|
+
- **Docker** -- testcontainers for `spec(app(...))`, docker compose for `spec(stack(...))`
|
|
354
|
+
- **vitest** -- peer dependency
|
|
355
|
+
- **hono** -- optional peer, only needed for `spec(app(...))` mode with in-process apps
|