first commit
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
# TestEnvironment
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`TestEnvironment` is the orchestrator for integration test suites that need several external services running concurrently. It accepts a named map of `ServiceFixture` instances, starts them all in parallel during `setup()`, records which ones failed to start, and stops the successful ones in reverse order during `teardown()`. This reverse-teardown order prevents cascading failures in services that depend on each other (e.g. stopping a connection pool before closing its database).
|
||||
|
||||
## Features
|
||||
|
||||
- **Parallel startup** — all fixtures are started with `Promise.allSettled`, so a slow service does not block faster ones.
|
||||
- **Graceful failure handling** — if a fixture's `start()` throws, its name is added to the `failed` list rather than propagating the error; tests can then use `skipIfUnavailable` to opt out.
|
||||
- **Reverse-order teardown** — fixtures are stopped in the reverse of their start-success order, respecting implicit dependency chains.
|
||||
- **Swallowed stop errors** — `stop()` errors are caught and discarded so a single broken fixture does not prevent the rest from cleaning up.
|
||||
- **Safe empty teardown** — calling `teardown()` before `setup()` (or when all fixtures failed) is a no-op.
|
||||
- **Protected internals** — `fixtures`, `options`, and `started` are `protected` to allow subclassing.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { TestEnvironment } from "@suaveplan/testing-integration/environment/test-environment";
|
||||
|
||||
const env = new TestEnvironment({
|
||||
redis: redisFixture,
|
||||
postgres: postgresFixture,
|
||||
});
|
||||
|
||||
const { configs, failed } = await env.setup();
|
||||
|
||||
if (failed.length > 0) {
|
||||
console.warn("Some fixtures failed to start:", failed);
|
||||
}
|
||||
|
||||
console.log(configs.redis); // { url: "redis://localhost:6380" }
|
||||
console.log(configs.postgres); // { host: "localhost", port: 5433 }
|
||||
|
||||
await env.teardown();
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
Using `TestEnvironment` with `skipIfUnavailable` via the global-setup bridge:
|
||||
|
||||
```ts
|
||||
// vitest.config.ts
|
||||
import { createIntegrationSetup } from "@suaveplan/testing-integration/setup/create-integration-setup";
|
||||
import { redisFixture } from "./fixtures/redis.js";
|
||||
import { postgresFixture } from "./fixtures/postgres.js";
|
||||
|
||||
const { globalSetup, inject } = createIntegrationSetup({
|
||||
redis: redisFixture,
|
||||
postgres: postgresFixture,
|
||||
});
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globalSetup: [globalSetup],
|
||||
},
|
||||
});
|
||||
|
||||
// my.test.ts
|
||||
import { beforeEach } from "vitest";
|
||||
import { skipIfUnavailable } from "@suaveplan/testing-integration/helpers/skip-if-unavailable";
|
||||
|
||||
beforeEach(skipIfUnavailable("redis"));
|
||||
|
||||
it("stores a value in redis", async () => {
|
||||
// guaranteed: redis was available when setup ran
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `TestEnvironment`
|
||||
|
||||
**Constructor:** `new TestEnvironment(fixtures, options?)`
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|---|---|---|
|
||||
| `fixtures` | `Record<string, ServiceFixture<…>>` | Named map of fixtures to manage. |
|
||||
| `options` | `TestEnvironmentOptions` | Optional configuration (currently `timeoutMs` reserved). |
|
||||
|
||||
**Methods:**
|
||||
|
||||
| Method | Signature | Description |
|
||||
|---|---|---|
|
||||
| `setup` | `() => Promise<TestEnvironmentSetupResult>` | Starts all fixtures in parallel. Returns configs and failed list. |
|
||||
| `teardown` | `() => Promise<void>` | Stops all started fixtures in reverse order. |
|
||||
|
||||
### `TestEnvironmentSetupResult`
|
||||
|
||||
| Property | Type | Description |
|
||||
|---|---|---|
|
||||
| `configs` | `Record<string, Record<string, unknown>>` | Config objects keyed by fixture key for successful fixtures. |
|
||||
| `failed` | `string[]` | Names of fixtures whose `start()` threw. |
|
||||
|
||||
### `TestEnvironmentOptions`
|
||||
|
||||
| Property | Type | Description |
|
||||
|---|---|---|
|
||||
| `timeoutMs` | `number?` | Reserved for future per-fixture start timeout. |
|
||||
|
||||
### `TestEnvironmentError`
|
||||
|
||||
Re-exported from `health-poller`. Use `instanceof TestEnvironmentError` to discriminate environment errors.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`Promise.allSettled` is used instead of `Promise.all` so that one failing fixture does not prevent others from starting. The index of each settled result maps 1:1 with the `entries` array, so the correct fixture name can be attributed to each failure.
|
||||
|
||||
The `started` array tracks fixture **names** (not keys) in insertion order. During teardown, names are reversed and matched against `Object.values(this.fixtures)` by name. This means duplicate names across different keys would cause a teardown ambiguity — callers should ensure fixture names are unique within a `TestEnvironment` instance.
|
||||
Reference in New Issue
Block a user