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
ActorSystemstandardmäßig mit einemNoopLogger— 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.
Die API
Abschnitt betitelt „Die API“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}TestKit.create(name?, options?)
Abschnitt betitelt „TestKit.create(name?, options?)“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.
tk.createTestProbe(options?)
Abschnitt betitelt „tk.createTestProbe(options?)“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.
tk.within(durationMs, callback)
Abschnitt betitelt „tk.within(durationMs, callback)“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.
tk.shutdown()
Abschnitt betitelt „tk.shutdown()“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.
TestKit.withManualScheduler()
Abschnitt betitelt „TestKit.withManualScheduler()“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 springenawait 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.
Fixture-Muster
Abschnitt betitelt „Fixture-Muster“Per-Test-Fixture (Bun, Vitest, Jest)
Abschnitt betitelt „Per-Test-Fixture (Bun, Vitest, Jest)“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.
Geteiltes Fixture für read-only-Tests
Abschnitt betitelt „Geteiltes Fixture für read-only-Tests“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.
Was TestKit nicht macht
Abschnitt betitelt „Was TestKit nicht macht“Wo es weitergeht
Abschnitt betitelt „Wo es weitergeht“- Testing — Überblick — das große Bild: Unit, zeit-deterministisch, multi-node.
- TestProbe — die Fake-Actor- Expectations-API.
- ManualScheduler — virtuelle Zeit.
- MultiNodeSpec — für Cluster-Szenarien.
Die TestKit-API-Referenz deckt die
volle Oberfläche ab.
