4.5 KiB
TestEnvironment
(no submodules)
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 thefailedlist rather than propagating the error; tests can then useskipIfUnavailableto 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()beforesetup()(or when all fixtures failed) is a no-op. - Protected internals —
fixtures,options, andstartedareprotectedto allow subclassing.
Usage: Basic Example
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:
// 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.