Zum Inhalt springen
Deutsch

TestKit

TestKit ist ein dünner Convenience-Wrapper um ActorSystem für Tests. Er gibt dir ein einsatzbereites System mit sinnvollen Test-Defaults — leiser Logger, einfache Probe-Erzeugung, deterministischer Shutdown.

import { TestKit } from 'actor-ts/testkit';
const tk = TestKit.create('my-spec');
const probe = tk.createTestProbe();
const ref = tk.system.spawnAnonymous(() => new Worker(probe));
ref.tell({ kind: 'do' });
await probe.expectMessage('done');
await tk.shutdown();

Drei Dinge, die das Kit für dich macht:

  • Baut das ActorSystem standardmäßig mit einem NoopLogger — kein Konsolen-Spam während Test-Läufen.
  • Stellt createTestProbe() bereit, sodass du das System nicht durch jeden Probe-Konstruktor fädeln musst.
  • Besitzt shutdown() — einmal am Test-Ende aufrufen; baut System, Scheduler und Dispatcher sicher ab.
class TestKit {
static create(name?: string, options?: TestKitOptions): TestKit;
static withManualScheduler(name?, options?): { kit: TestKit; scheduler: ManualScheduler };
readonly system: ActorSystem;
createTestProbe(options?: TestProbeOptions): TestProbe;
within<T>(durationMs: number, callback: () => Promise<T>): Promise<T>;
shutdown(): Promise<void>;
}
interface TestKitOptions extends ActorSystemOptionsType {
quiet?: boolean; // Default true — NoopLogger nutzen
}

Baut ein Kit mit frischem System. Der name ist der System-Name — nützlich, wenn parallele Test-Suites keinen geteilten State haben dürfen. Default ist 'test-kit'.

const testKitOptions = TestKitOptions.create()
.withQuiet(false) // Logging einschalten
.withLogLevel(LogLevel.Debug)
.withConfig({ /* HOCON-Overrides */ });
const tk = TestKit.create('my-spec', testKitOptions);

Mit quiet: false bekommst du Konsolen-Logs — nützlich beim Debuggen eines fehlschlagenden Tests. Andernfalls sind Log-Aufrufe No-Ops, was deine Test-Ausgabe sauber hält.

const testProbeOptions = TestProbeOptions.create()
.withDefaultTimeoutMs(5_000) // Default-Wartezeit für expect/receive
.withName('order-probe');
const probe = tk.createTestProbe(testProbeOptions); // sichtbar in Logs
ref.tell({ kind: 'do', replyTo: probe });
await probe.expectMessage({ kind: 'done' });

Gibt einen TestProbe zurück, gescoped auf das System dieses Kits. Siehe TestProbe für die volle Expectations-API.

await tk.within(1_000, async () => {
ref.tell({ kind: 'do' });
await probe.expectMessage('done');
});

Führe callback aus und assertete, dass es in weniger als durationMs abgeschlossen wurde. Wirft, wenn es länger gedauert hat. Nützlich für „das sollte schnell sein”-SLAs innerhalb von Tests, ohne vom Per-Test-Timeout des Test-Runners abzuhängen.

Hinweis: das ist eine weiche Deadline — callback läuft auch dann zu Ende, wenn es überzieht. Es wirft nur im Nachhinein.

afterEach(async () => {
await tk.shutdown();
});

Terminiert das zugrundeliegende System. Rufe das immer in einem Fixture-Teardown — ohne ihn leakt der Test-Prozess ein lebendes System, das die Event-Loop am Leben hält. Manche Test-Runner (Bun, Vitest) erkennen das und lassen die Suite fehlschlagen; andere hängen.

const { kit, scheduler } = TestKit.withManualScheduler('my-spec');
const ref = kit.system.spawnAnonymous(() => new Heartbeat(probe));
ref.tell({ kind: 'start' });
scheduler.advance(5_000); // 5 virtuelle Sekunden springen
await probe.expectMessage('tick');

Gibt sowohl das Kit als auch seinen ManualScheduler zurück, damit der Test virtuelle Zeit voranbringen kann. Siehe ManualScheduler für die volle Virtual-Clock-Semantik.

import { describe, it, beforeEach, afterEach } from 'bun:test';
describe('Counter', () => {
let tk: TestKit;
let probe: TestProbe;
beforeEach(() => {
tk = TestKit.create();
probe = tk.createTestProbe();
});
afterEach(async () => {
await tk.shutdown();
});
it('inkrementiert', async () => {
const ref = tk.system.spawnAnonymous(Counter);
ref.tell({ kind: 'increment' });
ref.tell({ kind: 'get', replyTo: probe as ActorRef<number> });
await probe.expectMessage(1);
});
});

Ein frisches Kit pro Test — kein State-Leakage zwischen Tests, leicht langsamer als ein geteiltes Kit, aber wert für die Isolation.

describe('PureBehaviorTests', () => {
let tk: TestKit;
beforeAll(() => { tk = TestKit.create(); });
afterAll(async () => { await tk.shutdown(); });
// jedes `it` nutzt tk.createTestProbe() für seine eigene Probe, aber
// das zugrundeliegende ActorSystem wird geteilt.
});

Schneller als Per-Test-Setup; nur sicher, wenn Tests keine geteilten Actor mutieren.

Die TestKit-API-Referenz deckt die volle Oberfläche ab.