@dotpress/testing 2.0.0-alpha.2 → 2.0.0-alpha.4
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 +18 -91
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,108 +1,35 @@
|
|
|
1
1
|
# @dotpress/testing
|
|
2
2
|
|
|
3
|
-
Testing helpers for [Dotpress](https://dotpress.dev): a test app, the adapter conformance suite, and assertions for events and
|
|
3
|
+
Testing helpers for [Dotpress](https://dotpress.dev): a test app in memory, the adapter conformance suite, simulated AWS Lambda events, a memory logger, and assertions for events and emails.
|
|
4
4
|
|
|
5
5
|
> 🚧 Dotpress V2 is in alpha (`2.0.0-alpha`): the API may still change before 2.0.0.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Every Dotpress HTTP adapter must pass the conformance suite, which checks that routing, request parsing, validation, errors, middlewares and responses (including streams and Server-Sent Events) behave the same on every server.
|
|
10
|
-
|
|
11
|
-
```ts
|
|
12
|
-
// conformance.test.ts (run with vitest)
|
|
13
|
-
import type { AddressInfo } from 'node:net'
|
|
14
|
-
import { createApp } from '@dotpress/core'
|
|
15
|
-
import { createHttpTarget, runConformanceSuite } from '@dotpress/testing'
|
|
16
|
-
import { myAdapter } from '../src/index.js'
|
|
17
|
-
|
|
18
|
-
runConformanceSuite({
|
|
19
|
-
name: 'my-adapter',
|
|
20
|
-
async setup(options) {
|
|
21
|
-
const adapter = myAdapter()
|
|
22
|
-
const app = await createApp({ ...options, adapter })
|
|
23
|
-
await app.listen(0, '127.0.0.1')
|
|
24
|
-
|
|
25
|
-
const { port } = adapter.server().address() as AddressInfo
|
|
26
|
-
return createHttpTarget(`http://127.0.0.1:${port}`, () => app.close())
|
|
27
|
-
},
|
|
28
|
-
})
|
|
7
|
+
```sh
|
|
8
|
+
npm install --save-dev @dotpress/testing
|
|
29
9
|
```
|
|
30
10
|
|
|
31
|
-
`setup()` is called once per test with the app options of the scenario. Adapters that do not listen on a port return `createFetchTarget(handle, close)`, where `handle` takes a Web `Request` and returns a `Response` (in-memory `fetch()`).
|
|
32
|
-
|
|
33
|
-
### AWS Lambda
|
|
34
|
-
|
|
35
|
-
`createLambdaTarget(handler, { format, close })` sends each request to a Lambda handler as a simulated event, and converts the result back: `format` is `'v1'` (API Gateway REST API), `'v2'` (HTTP API, Function URLs) or `'alb'` (Application Load Balancer, with multi-value headers). A failed invocation gets a `502` response, like API Gateway. `toLambdaEvent()` and `fromLambdaResult()` are exported for handler tests.
|
|
36
|
-
|
|
37
11
|
```ts
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
// Responses sent once complete: skips the scenarios that need incremental
|
|
41
|
-
// streaming or client disconnects.
|
|
42
|
-
bufferedResponses: true,
|
|
43
|
-
setup: async (options) => {
|
|
44
|
-
let app
|
|
45
|
-
const handler = createLambdaHandler(async () => (app = await createApp({ ...options, adapter: myAdapter() })))
|
|
46
|
-
return createLambdaTarget(handler, { format: 'v2', close: async () => app?.close() })
|
|
47
|
-
},
|
|
48
|
-
})
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## Memory logger
|
|
12
|
+
import { createTestApp } from '@dotpress/testing'
|
|
13
|
+
import { expect, it } from 'vitest'
|
|
52
14
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
```ts
|
|
56
|
-
const logger = createMemoryLogger()
|
|
57
|
-
await createApp({ adapter, logger })
|
|
58
|
-
expect(logger.entries).toContainEqual({ level: 'error', args: [error, 'Unhandled error while processing request'] })
|
|
59
|
-
```
|
|
15
|
+
it('creates a task', async () => {
|
|
16
|
+
const api = await createTestApp({ routers: [tasksRouter] })
|
|
60
17
|
|
|
61
|
-
|
|
18
|
+
const res = await api.fetch('/tasks', {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: { 'content-type': 'application/json' },
|
|
21
|
+
body: JSON.stringify({ title: 'Write the tests' }),
|
|
22
|
+
})
|
|
62
23
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
import { createFakeEventBus } from '@dotpress/events'
|
|
67
|
-
import { expectEventEmitted, expectEventNotEmitted } from '@dotpress/testing'
|
|
68
|
-
|
|
69
|
-
const events = createFakeEventBus()
|
|
70
|
-
await signUp(events, 'ada@example.com')
|
|
71
|
-
|
|
72
|
-
expectEventEmitted(events, userCreated, { email: 'ada@example.com' })
|
|
73
|
-
expectEventEmitted(events, 'user.created', undefined, { times: 1, metadata: { requestId: 'req-1' } })
|
|
74
|
-
expectEventNotEmitted(events, userDeleted)
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
- `event`: the definition, or its name.
|
|
78
|
-
- `payload`: objects are matched partially (like `toMatchObject()`), other values exactly; asymmetric matchers are accepted (`expect.any(String)`). Without it, any payload matches.
|
|
79
|
-
- `options`: `times` (exact number of emissions; at least one by default), `metadata` (matched partially), `delay` (fake event bus only).
|
|
80
|
-
|
|
81
|
-
With the Dotpress plugin, pass the fake bus to `eventsPlugin()`: `ctx.events` records into it, with the `requestId` in the metadata.
|
|
82
|
-
|
|
83
|
-
## Mail assertions
|
|
84
|
-
|
|
85
|
-
`expectMailSent(outbox, criteria?, options?)` asserts that a message was sent through the memory provider of the mailer (`createMemoryProvider()` of `@dotpress/mailer`), and returns the last matching message.
|
|
86
|
-
|
|
87
|
-
```ts
|
|
88
|
-
import { createMailer, createMemoryProvider } from '@dotpress/mailer'
|
|
89
|
-
import { expectMailSent, expectNoMailSent } from '@dotpress/testing'
|
|
90
|
-
|
|
91
|
-
const outbox = createMemoryProvider()
|
|
92
|
-
const mailer = createMailer({ provider: outbox, from: 'noreply@example.com' })
|
|
93
|
-
await signUp(mailer, 'ada@example.com')
|
|
94
|
-
|
|
95
|
-
const mail = expectMailSent(outbox, { to: 'ada@example.com', template: welcomeMail, locale: 'en' })
|
|
96
|
-
expect(mail.html).toContain('https://example.com/confirm')
|
|
97
|
-
expectNoMailSent(outbox, { to: 'grace@example.com' })
|
|
24
|
+
expect(res.status).toBe(201)
|
|
25
|
+
await api.close()
|
|
26
|
+
})
|
|
98
27
|
```
|
|
99
28
|
|
|
100
|
-
|
|
101
|
-
- `template`: the template, or its name.
|
|
102
|
-
- `subject`, `html`, `text`, `locale`, `tags`, `metadata`, `headers`, `attachments`: matched like `toMatchObject()`, with asymmetric matchers (`expect.stringContaining('invoice')`).
|
|
103
|
-
- `options.times`: exact number of messages; at least one by default.
|
|
29
|
+
## Documentation
|
|
104
30
|
|
|
105
|
-
|
|
31
|
+
- [Testing routes](https://dotpress.dev/routing/testing): the test app, authenticated requests, a real server
|
|
32
|
+
- [Testing helpers](https://dotpress.dev/services/testing): events, emails, logs, AWS Lambda, the conformance suite for adapters
|
|
106
33
|
|
|
107
34
|
## License
|
|
108
35
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotpress/testing",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.4",
|
|
4
4
|
"description": "Testing helpers for Dotpress: test app, conformance suite, event and mail assertions",
|
|
5
5
|
"author": "Renaud Guyon",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,11 +30,11 @@
|
|
|
30
30
|
"node": ">=22.12"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"@dotpress/core": "^2.0.0-alpha.
|
|
33
|
+
"@dotpress/core": "^2.0.0-alpha.4",
|
|
34
34
|
"vitest": "^5.0.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@dotpress/core": "2.0.0-alpha.
|
|
37
|
+
"@dotpress/core": "2.0.0-alpha.4",
|
|
38
38
|
"zod": "^4.6.7"
|
|
39
39
|
},
|
|
40
40
|
"keywords": [
|