Files
skills/docs/testkit/testing/testing-integration/src/environment/test-environment.md
2026-05-29 08:20:56 +01:00

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 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

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.