first commit
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
# assertMail
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`assertMail()` is a test assertion helper that polls the Mailpit REST API and fails tests that do not receive an expected email within a configurable timeout. It bridges the gap between sending an email via SMTP and verifying delivery in integration tests.
|
||||
|
||||
The helper is designed for use with `MailpitFixture` but works with any Mailpit instance accessible at a known HTTP URL. Its injectable `fetchFn` option makes it fully unit-testable without a live Mailpit server.
|
||||
|
||||
## Features
|
||||
|
||||
- **Polling-based**: retries at a configurable interval until the deadline, tolerating normal async email delivery latency.
|
||||
- **Exact address matching**: verifies `To[].Address` exactly matches the provided `to` address.
|
||||
- **Substring subject matching**: checks that the message `Subject` includes the provided `subject` string, allowing partial matches.
|
||||
- **Configurable timeout and interval**: `timeoutMs` and `pollIntervalMs` options allow fast-failing in unit tests (e.g. 50ms timeout) and longer waits in real environments.
|
||||
- **Injectable fetch**: the `fetchFn` option replaces `globalThis.fetch` for deterministic unit tests with no live network dependency.
|
||||
- **Structured error message**: the timeout error includes the `to` and `subject` criteria and timeout value for easy debugging.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```typescript
|
||||
import { assertMail } from "@suaveplan/testing-integration-mailpit";
|
||||
|
||||
// After sending an email via SMTP...
|
||||
await assertMail("http://localhost:8025", {
|
||||
to: "user@example.com",
|
||||
subject: "Welcome",
|
||||
});
|
||||
// Resolves when a matching message is found; throws after 10s otherwise.
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```typescript
|
||||
import {
|
||||
MailpitFixture,
|
||||
assertMail,
|
||||
} from "@suaveplan/testing-integration-mailpit";
|
||||
import nodemailer from "nodemailer";
|
||||
|
||||
const fixture = new MailpitFixture();
|
||||
const config = await fixture.start();
|
||||
|
||||
const transporter = nodemailer.createTransport({ url: config.smtpUrl });
|
||||
await transporter.sendMail({
|
||||
from: "noreply@example.com",
|
||||
to: "alice@example.com",
|
||||
subject: "Password Reset Request",
|
||||
text: "Click here to reset your password.",
|
||||
});
|
||||
|
||||
// Custom timeout: wait up to 5 seconds, polling every 200ms
|
||||
await assertMail(config.apiUrl, {
|
||||
to: "alice@example.com",
|
||||
subject: "Password Reset",
|
||||
}, {
|
||||
timeoutMs: 5000,
|
||||
pollIntervalMs: 200,
|
||||
});
|
||||
|
||||
await fixture.stop();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `assertMail(apiUrl, criteria, options?): Promise<void>`
|
||||
|
||||
Polls the Mailpit API until a matching message is found or the timeout elapses.
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `apiUrl` | `string` | Mailpit API base URL (e.g. `http://localhost:8025`). |
|
||||
| `criteria` | `AssertMailCriteria` | Matching criteria. |
|
||||
| `options?` | `AssertMailOptions` | Optional configuration. |
|
||||
|
||||
#### Throws
|
||||
|
||||
`Error` when no matching message is found within `timeoutMs`. The message includes the `to` and `subject` values and elapsed timeout.
|
||||
|
||||
### `interface AssertMailCriteria`
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `to` | `string` | Exact recipient address to match in `To[].Address`. |
|
||||
| `subject` | `string` | Subject substring to match via `String.includes()`. |
|
||||
|
||||
### `interface AssertMailOptions`
|
||||
|
||||
| Field | Type | Default | Description |
|
||||
|-------|------|---------|-------------|
|
||||
| `timeoutMs?` | `number` | `10_000` | Total wait time in ms. |
|
||||
| `pollIntervalMs?` | `number` | `500` | Polling interval in ms. |
|
||||
| `fetchFn?` | `typeof fetch` | `globalThis.fetch` | Injectable fetch for unit tests. |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- **Polling strategy**: the helper uses a `while (Date.now() < deadline)` loop with `setTimeout` between iterations. This provides predictable behavior without relying on `setInterval` side effects.
|
||||
- **Mailpit API shape**: the `/api/v1/messages` endpoint returns `{ messages: Array<{ To: [{Address: string}], Subject: string, ... }> }`. The `messages` key may be absent on an empty inbox; the helper defaults to `[]`.
|
||||
- **Partial subject matching rationale**: email subjects often contain dynamic content (timestamps, user names) appended to a fixed prefix. Substring matching lets tests assert on the stable prefix without hardcoding dynamic parts.
|
||||
- **Exact address matching rationale**: recipient addresses are authoritative identifiers. Substring matching on addresses would be error-prone (e.g. `user@example.com` matching `user@example.company`).
|
||||
Reference in New Issue
Block a user