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(Props.create(() => new Worker(probe)));
ref.tell({ kind: 'do' });
await probe.expectMsg('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, opts?: TestKitOptions): TestKit;
static withManualScheduler(name?, opts?): { kit: TestKit; scheduler: ManualScheduler };
readonly system: ActorSystem;
createTestProbe(opts?: TestProbeOptions): TestProbe;
within<T>(durationMs: number, fn: () => Promise<T>): Promise<T>;
shutdown(): Promise<void>;
}
interface TestKitOptions extends ActorSystemSettings {
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 tk = TestKit.create('my-spec', {
quiet: false, // Logging einschalten
logLevel: LogLevel.Debug,
config: { /* HOCON-Overrides */ },
});

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 probe = tk.createTestProbe({
defaultTimeoutMs: 5_000, // Default-Wartezeit für expect/receive
name: 'order-probe', // sichtbar in Logs
});
ref.tell({ kind: 'do', replyTo: probe });
await probe.expectMsg({ 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.expectMsg('done');
});

Führe fn 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 — fn 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(Props.create(() => new Heartbeat(probe)));
ref.tell({ kind: 'start' });
scheduler.advance(5_000); // 5 virtuelle Sekunden springen
await probe.expectMsg('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(Props.create(() => new Counter()));
ref.tell({ kind: 'inc' });
ref.tell({ kind: 'get', replyTo: probe as ActorRef<number> });
await probe.expectMsg(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.