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

4.3 KiB

createIntegrationSetup

Purpose

createIntegrationSetup is the primary entry point for wiring integration test fixtures into the Vitest global-setup lifecycle. It accepts a named map of ServiceFixture instances, registers them in a module-level registry that global-setup.ts reads at startup, and returns two things: the absolute path of the Vitest global-setup module (ready to drop into test.globalSetup) and a typed inject helper that test files use to retrieve fixture configs. This one-call configuration model means consumers never need to import from global-setup.ts directly.

Features

  • Typed inject helper — the return type of inject(key) is inferred from the fixture's TConfig type parameter via InferConfig, so no type casts are needed in test files.
  • Auto-resolved globalSetup path — the path is derived from import.meta.url, so it works regardless of where the consumer's vitest.config.ts lives.
  • Additive registry — multiple calls to createIntegrationSetup accumulate fixtures into a shared registry; this allows split configuration across multiple config files.
  • Injectable test double — createIntegrationSetupWithInject (under the package-private internal/ directory) exposes the factory with a replaceable injectFn for unit testing without real Vitest provide/inject infrastructure.
  • Zero runtime overhead — the function does no async work; startup cost is deferred entirely to global-setup.ts.

Usage: Basic Example

// vitest.config.ts
import { defineConfig } from "vitest/config";
import { createIntegrationSetup } from "@suaveplan/testing-integration/setup/create-integration-setup";
import { redisFixture } from "./fixtures/redis.js";

const { globalSetup, inject } = createIntegrationSetup({ redis: redisFixture });

export default defineConfig({
  test: {
    globalSetup: [globalSetup],
  },
});

export { inject };

// redis.test.ts
import { inject } from "../vitest.config.js";
import { beforeEach } from "vitest";
import { skipIfUnavailable } from "@suaveplan/testing-integration/helpers/skip-if-unavailable";

beforeEach(skipIfUnavailable("redis"));

it("connects to redis", async () => {
  const { url } = inject("redis"); // typed as RedisConfig
  // ...
});

Usage: Advanced Example

Accumulating fixtures across multiple config calls:

// vitest.config.ts
import { createIntegrationSetup } from "@suaveplan/testing-integration/setup/create-integration-setup";

// Split fixture definitions across modules
const { globalSetup: gsA } = createIntegrationSetup({ redis: redisFixture });
const { globalSetup: gsB, inject } = createIntegrationSetup({ postgres: postgresFixture });

// Both fixture sets are registered in the shared package-private fixtureRegistry
export default defineConfig({
  test: {
    globalSetup: [gsA], // only one path needed — registry is shared
  },
});

API Reference

createIntegrationSetup(fixtures)

Parameters:

  • fixtures: Record<string, ServiceFixture<…>> — named map of fixtures to register.

Returns: IntegrationSetupResult<TFixtures>

IntegrationSetupResult<TFixtures>

Property Type Description
globalSetup string Absolute path to global-setup.js. Add to test.globalSetup.
inject <K extends keyof TFixtures>(key: K) => InferConfig<TFixtures[K]> Typed accessor for fixture configs populated by global-setup.

Package-private internals

createIntegrationSetupWithInject(fixtures, injectFn) and the fixtureRegistry Map live under src/internal/ and are deliberately NOT exported via package.json exports. They are reachable only from within this package's own source tree (used by tests and by the public façade above) and consumers MUST NOT rely on them.

Implementation Notes

The globalSetup path is computed via new URL("./global-setup.js", import.meta.url).pathname. This resolves relative to the compiled output location so the path is always valid after vite build. It does not depend on __dirname or any Node.js path resolution, making it compatible with ES module environments.

The inject closure captures injectFn at creation time and calls it lazily — only when inject(key) is invoked inside a test. This lazy evaluation is necessary because Vitest's inject only returns values after the global-setup provide calls have run.