first commit
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
# ServiceFixture
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`ServiceFixture<TConfig>` is the foundational contract for integration test service fixtures in Suaveplan. It defines a three-phase lifecycle — `start`, `healthCheck`, and `stop` — that allows `TestEnvironment` to manage any number of external services (containers, mock servers, in-process stores, etc.) uniformly. By coding against this interface rather than concrete implementations, consumers can swap fixture strategies without changing test setup code.
|
||||
|
||||
## Features
|
||||
|
||||
- **Generic configuration output** — `start()` returns a caller-defined `TConfig` (e.g. `{ url: string; port: number }`) so consuming tests receive strongly-typed connection details.
|
||||
- **Three-phase lifecycle** — explicit `start` / `stop` / `healthCheck` methods map directly to `TestEnvironment`'s parallel startup and reverse-order teardown algorithms.
|
||||
- **`InferConfig<F>` utility type** — extracts the `TConfig` type parameter from any `ServiceFixture` implementation at compile time, enabling generic helper functions.
|
||||
- **Zero runtime code** — the module is types-only; no JavaScript is emitted for this file, so it contributes nothing to bundle size.
|
||||
- **Idempotent stop contract** — implementations are required to tolerate repeated `stop()` calls without throwing.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import type { ServiceFixture } from "@suaveplan/testing-integration/fixture/service-fixture";
|
||||
|
||||
interface RedisConfig {
|
||||
url: string;
|
||||
port: number;
|
||||
}
|
||||
|
||||
class MemoryRedisFixture implements ServiceFixture<RedisConfig> {
|
||||
readonly name = "memory-redis";
|
||||
|
||||
async start(): Promise<RedisConfig> {
|
||||
// spin up an in-process redis-compatible store
|
||||
return { url: "redis://localhost", port: 6380 };
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
// flush and close the store
|
||||
}
|
||||
|
||||
async healthCheck(): Promise<boolean> {
|
||||
return true; // in-process is always ready
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
Using `InferConfig` to write generic helpers that work with any fixture:
|
||||
|
||||
```ts
|
||||
import type {
|
||||
InferConfig,
|
||||
ServiceFixture,
|
||||
} from "@suaveplan/testing-integration/fixture/service-fixture";
|
||||
|
||||
interface PostgresConfig {
|
||||
host: string;
|
||||
port: number;
|
||||
database: string;
|
||||
}
|
||||
|
||||
interface PostgresFixture extends ServiceFixture<PostgresConfig> {
|
||||
readonly runMigrations: () => Promise<void>;
|
||||
}
|
||||
|
||||
// InferConfig pulls PostgresConfig out of PostgresFixture at compile time
|
||||
type Config = InferConfig<PostgresFixture>; // { host: string; port: number; database: string }
|
||||
|
||||
function buildConnectionString<F extends ServiceFixture<Record<string, unknown>>>(
|
||||
config: InferConfig<F> & { host: string; port: number },
|
||||
): string {
|
||||
return `${config.host}:${config.port}`;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `ServiceFixture<TConfig>`
|
||||
|
||||
| Member | Type | Description |
|
||||
|---|---|---|
|
||||
| `name` | `readonly string` | Human-readable identifier used in error messages and the fixture registry. |
|
||||
| `start()` | `() => Promise<TConfig>` | Starts the service and returns connection config. Called by `TestEnvironment.setup()`. |
|
||||
| `stop()` | `() => Promise<void>` | Stops the service and releases resources. Must be idempotent. |
|
||||
| `healthCheck()` | `() => Promise<boolean>` | Returns `true` when the service is ready; `false` otherwise. Must not throw. |
|
||||
|
||||
### `InferConfig<F>`
|
||||
|
||||
Extracts `TConfig` from `ServiceFixture<TConfig>`. Returns `never` for any type that is not a `ServiceFixture` subtype.
|
||||
|
||||
**Type parameters:**
|
||||
|
||||
- `F` — the concrete fixture type to inspect
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`TConfig` is constrained to `Record<string, unknown>` rather than a plain `object` to ensure callers always return a plain serialisable map — not a class instance or primitive. This constraint is enforced at the type level only; no runtime validation is performed here (validation lives in `TestEnvironment`).
|
||||
|
||||
The `healthCheck` method is intentionally separate from `start` to support fixtures that need to start a process first and then wait for it to become healthy (e.g. a Docker container). `HealthPoller` uses this method in a polling loop with configurable timeout and interval.
|
||||
|
||||
`InferConfig` is implemented as a conditional type (`F extends ServiceFixture<infer C> ? C : never`) rather than a mapped type to keep inference simple and to return `never` for non-fixture types — a common pattern when building generic test helpers.
|
||||
Reference in New Issue
Block a user