MultiNodeSpec
MultiNodeSpec lässt N Cluster-Nodes innerhalb eines
Test-Prozesses laufen. Jeder ist ein echtes ActorSystem; sie
kommunizieren via InMemoryTransport (kein TCP). Nützlich zum
Testen von Cluster-Verhalten — Sharding, Singletons, Gossip-
Konvergenz, Failover — ohne Docker.
import { MultiNodeSpec } from 'actor-ts/testkit';
const spec = await MultiNodeSpec.create({ systemName: 'cluster-spec', nodes: 3,});
// spec.nodes ist ein Array von ActorSystems, alle in einen Cluster gejoint:const node1 = spec.nodes[0];const node2 = spec.nodes[1];
// Spawne Actor auf verschiedenen Nodes:const probe = spec.createTestProbe(0);const remote = node2.spawnAnonymous(Props.create(() => new Worker()));
remote.tell({ kind: 'do', replyTo: probe });await probe.expectMsg({ kind: 'done' });
await spec.shutdown();Wann nutzen
Abschnitt betitelt „Wann nutzen“Drei primäre Fälle:
- Cluster-Verhalten testen — Sharding-Rebalances, Singleton-Failover, Gossip-Konvergenz — diese ohne echtes Netzwerk-Setup verifizieren.
- Verteilte Bugs reproduzieren — leichter zu isolieren, wenn der gesamte Cluster in einem Prozess läuft.
- CI-freundliche Cluster-Tests — schnell (sub-Sekunde), kein Docker, keine Ports.
Für Tests, die echtes Netzwerkverhalten brauchen (TCP- Semantik, TLS, Cross-Host-Latenz), nutze ParallelMultiNodeSpec oder externes Docker-Compose.
Konfiguration
Abschnitt betitelt „Konfiguration“interface MultiNodeSpecSettings { systemName: string; nodes: number; roles?: Array<string | undefined>; // Per-Node-Rollen config?: Record<string, unknown>;}| Feld | Zweck |
|---|---|
systemName | Logischer Name — erscheint in Actor-Pfaden. |
nodes | Anzahl der hochzufahrenden Cluster-Nodes. |
roles | Per-Node-Rollen-Tags. |
config | HOCON-Overrides für alle Nodes. |
Per-Node-Rollen
Abschnitt betitelt „Per-Node-Rollen“const spec = await MultiNodeSpec.create({ systemName: 'cluster-spec', nodes: 3, roles: ['frontend', 'compute', 'compute'],});
// Nun:// Node 0 hat Rolle 'frontend'// Node 1 + 2 haben Rolle 'compute'Nützlich, um rollen-gefilterte Allokation zu testen:
cluster1.sharding.start({ // ... role: 'compute', // → nur Nodes 1 + 2 hosten Shards});Was geteilt wird
Abschnitt betitelt „Was geteilt wird“Alle Nodes teilen sich:
- Denselben
InMemoryTransport-Bus — sie können miteinander reden. - Dasselbe Gossip-Protokoll — Membership konvergiert.
- Unabhängige Actor-Systeme — separate Dispatcher, Scheduler, Supervisor-Bäume.
Das heißt:
- Echte Cluster-Semantik — Mitglieder kommen hoch, Gossip konvergiert, Failure-Detector beobachtet, Sharding rebalancet.
- Keine Serialisierung — Nachrichten zwischen „Nodes” werden per Referenz übergeben (in-process). Kein Fit zum Testen serialisierungs-abhängigen Verhaltens.
Failover-Tests
Abschnitt betitelt „Failover-Tests“// Singleton-Failover verifizieren:const spec = await MultiNodeSpec.create({ systemName: 'spec', nodes: 3 });
// ... Singleton starten ...
// Den Host-Node finden:const hostNode = ...; // via cluster.state.leader identifizieren
// Failure simulieren:await spec.terminateNode(hostNode);
// Auf Failover warten:await new Promise(r => setTimeout(r, 5_000));
// Verifizieren, dass der Singleton umgezogen ist:const newHost = ...;expect(newHost).not.toBe(hostNode);
await spec.shutdown();spec.terminateNode(index) terminiert einen spezifischen Node
— die anderen beobachten den Unreachable-Status, gossipen die
Änderung, lösen Downing + Failover aus.
Per-Node-TestProbe
Abschnitt betitelt „Per-Node-TestProbe“const probe0 = spec.createTestProbe(0); // Probe auf Node 0const probe1 = spec.createTestProbe(1); // Probe auf Node 1Jede Probe ist an das Actor-System eines Nodes gebunden. Nützlich beim Testen von Routing — verifizieren, dass eine Nachricht auf dem erwarteten Node landet.
Sharding-Tests
Abschnitt betitelt „Sharding-Tests“const spec = await MultiNodeSpec.create({ nodes: 3 });
const regions = spec.nodes.map(sys => /* Cluster-Ref */.sharding.start<Cmd>({ typeName: 'entity', entityProps: Props.create(() => new Entity()), extractEntityId: (msg) => msg.id, }));
// Entities spawnen; verifizieren, dass sie sich über Nodes verteilen:for (const id of ['e1', 'e2', 'e3', 'e4', 'e5']) { regions[0].tell({ id, kind: 'wake-up' });}
// Placement inspizieren:const placement = await regions[0].ask({ kind: 'list-shards', replyTo: ... });expect(placement.regions.size).toBeGreaterThan(1); // verteilt
await spec.shutdown();Der Koordinator des Frameworks verteilt Shards über die Multi-Node-Spec wie in einem echten Cluster.
Was MultiNodeSpec nicht testet
Abschnitt betitelt „Was MultiNodeSpec nicht testet“Wo es weitergeht
Abschnitt betitelt „Wo es weitergeht“- Testing — Überblick — das größere Bild.
- TestKit — Single-System- Testing.
- ParallelMultiNodeSpec — die Multi-Prozess-Variante.
- Cluster — Überblick — was MultiNodeSpec simuliert.