Page actions

MockAgentTransport

MockAgentTransport is a deterministic AgentTransport implementation for tests. It records stream calls, lets tests emit LangGraph stream events manually, and avoids a running LangGraph server.

Complete test example

The pattern below covers the full lifecycle: provide a transport instance, create the component, emit stream events, and assert signal state.

import { Component } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { AgentError } from '@threadplane/chat';
import {
  provideAgent,
  MockAgentTransport,
  injectAgent,
} from '@threadplane/langgraph';
 
@Component({ template: '' })
class ChatComponent {
  readonly chat = injectAgent();
}
 
describe('ChatComponent', () => {
  let transport: MockAgentTransport;
 
  beforeEach(() => {
    transport = new MockAgentTransport();
    TestBed.configureTestingModule({
      imports: [ChatComponent],
      providers: [
        provideAgent({
          apiUrl: '',
          assistantId: 'test-agent',
          threadId: 'thread-1',
          transport,
        }),
      ],
    });
  });
 
  it('reflects streamed messages', async () => {
    const fixture = TestBed.createComponent(ChatComponent);
    fixture.detectChanges();
 
    const stream = fixture.componentInstance.chat.submit({ message: 'Hello' });
    await transport.emit([
      {
        type: 'values',
        messages: [{ type: 'ai', content: 'Hi there' }],
      },
    ]);
    await transport.close();
    await stream;
    fixture.detectChanges();
 
    // submit() appends the user message optimistically, so the streamed
    // assistant reply is the last entry, not the only one.
    expect(fixture.componentInstance.chat.messages()).toEqual([
      expect.objectContaining({ role: 'user', content: 'Hello' }),
      expect.objectContaining({ role: 'assistant', content: 'Hi there' }),
    ]);
    expect(fixture.componentInstance.chat.status()).toBe('idle');
  });
 
  it('surfaces errors through the error signal', async () => {
    const fixture = TestBed.createComponent(ChatComponent);
    fixture.detectChanges();
 
    const stream = fixture.componentInstance.chat.submit({ message: 'Hello' });
    await transport.emitError(new Error('not found'));
    await stream;
    fixture.detectChanges();
 
    expect(fixture.componentInstance.chat.status()).toBe('error');
    expect(fixture.componentInstance.chat.error()).toBeInstanceOf(AgentError);
  });
});

MockAgentTransport API

MethodDescription
constructor(script?: StreamEvent[][])Optionally seeds scripted event batches for manual stepping (defaults to []).
nextBatch()Returns the next scripted event batch.
emit(events)Pushes one or more StreamEvent objects into the active stream. Returns a promise that resolves once the adapter has consumed the batch.
emitError(err)Makes the active stream reject with err. The runtime catches it, so submit() still resolves — assert on status() and error() instead of on a rejected promise. Returns a promise that resolves once the stream has thrown.
close()Closes the active stream after queued events drain. Returns a promise that resolves once the run has finished.
flush()Resolves once everything emitted so far has been consumed, without emitting anything new.
isStreaming()Returns whether a stream is currently active.

Messages inside a values event are raw LangGraph messages, so their role comes from type ('human', 'ai', 'tool', 'system'), not from a role field. The projector reads _getType() or the raw type and falls back to 'ai'; a role key is never read.

The transport also records calls in streams, createdQueuedRuns, cancelledRuns, joinedRuns, and historyCalls, which is useful for asserting payloads and options.

Tip: Deterministic tests

MockAgentTransport emits only when your test calls emit(), emitError(), or close(). This makes stream state and payload assertions deterministic.

Warning: Await every emit

emit(), emitError(), close() and flush() are asynchronous. stream() is an async generator, so nothing has reached the signals at the moment emit() returns. Await the promise before asserting — a spec that reads signals synchronously after an emit reads stale state and passes or fails for the wrong reason. flush() covers the same wait when the agent was driven by something other than an emit.

What's Next

MockAgentTransportclass

Test transport for deterministic agent testing without a real LangGraph server. Script event batches upfront, then emit them manually or step through them in your test specs. Supports error injection and close control. `emit()`, `emitError()`, `close()` and `flush()` are awaitable: the returned promise settles once the adapter has consumed everything queued so far (and one macrotask later, so throttled signal writes have landed), which removes the hand-rolled `await new Promise(resolve => setTimeout(resolve, 0))` flush from specs.

Parameters

ParameterTypeDescription
script?StreamEvent[][]Array of event batches. Each batch is emitted as a group.

Properties

ParameterTypeDescription
cancelledRunsobject[]
childStateCallsobject[]
childStatesMap<string, ThreadState<DefaultValues>>Child-namespace checkpoints served by `getChildState`, keyed by checkpoint namespace.
createdQueuedRunsAgentQueueEntry<unknown>[]
historyThreadState<DefaultValues>[]
historyCallsstring[]
joinedRunsobject[]
streamsobject[]

Methods

cancelRun(threadId: string, runId: string, signal: AbortSignal): Promise<void>

Optional: cancel a server-side run.

ParameterTypeDescription
threadIdstring
runIdstring
signalAbortSignal
close(): Promise<void>

Close the stream. Remaining queued events are drained before completion. Resolves once the run has finished.

createQueuedRun(_assistantId: string, threadId: string, payload: unknown, signal: AbortSignal, options: LangGraphSubmitOptions): Promise<AgentQueueEntry<unknown>>

Optional: create a server-side queued run without joining it immediately.

ParameterTypeDescription
_assistantIdstring
threadIdstring
payloadunknown
signalAbortSignal
options?LangGraphSubmitOptions
emit(events: StreamEvent[]): Promise<void>

Manually emit events into the stream. Await the returned promise: it resolves once the adapter has pulled this batch out of the stream (or the run has ended), so signals are settled and assertions read live state rather than the value from before the emit.

ParameterTypeDescription
eventsStreamEvent[]
emitError(err: Error): Promise<void>

Inject an error into the stream. Resolves once the stream has thrown.

ParameterTypeDescription
errError
flush(): Promise<void>

Resolve once everything emitted so far has been consumed, without emitting anything new. Useful after driving the agent by some other route (a `submit()`, a `switchThread()`) that has to reach the transport first.

getChildState(threadId: string, checkpointNs: string, signal: AbortSignal): Promise<ThreadState<DefaultValues> | undefined>

Optional: read the newest checkpoint a child graph saved under one checkpoint namespace of a thread (for a delegation tool call, `tools:<task id>`). Used to restore a subagent's transcript when a thread is reloaded; without it, a reloaded subagent shows its status and result but no messages. Resolve `undefined` when the namespace has no saved state.

ParameterTypeDescription
threadIdstring
checkpointNsstring
signalAbortSignal
getHistory(threadId: string, signal: AbortSignal): Promise<ThreadState<DefaultValues>[]>

Optional: load persisted checkpoint history for a thread.

ParameterTypeDescription
threadIdstring
signalAbortSignal
isStreaming(): boolean

Returns true if a stream is currently active.

joinStream(threadId: string, runId: string, lastEventId: string | undefined, signal: AbortSignal): AsyncIterable<StreamEvent>

Optional: join an already-started run without creating a new one.

ParameterTypeDescription
threadIdstring
runIdstring
lastEventIdstring | undefined
signalAbortSignal
nextBatch(): StreamEvent[]

Advance to the next scripted batch. Pass the returned events to `emit()`.

stream(_assistantId: string, _threadId: string | null, _payload: unknown, signal: AbortSignal, options: LangGraphSubmitOptions): AsyncIterable<StreamEvent>

Open a streaming connection to an agent and yield events.

ParameterTypeDescription
_assistantIdstring
_threadIdstring | null
_payloadunknown
signalAbortSignal
options?LangGraphSubmitOptions

Examples

const transport = new MockAgentTransport([
  [{ type: 'values', messages: [aiMsg('Hello')] }],
  [{ type: 'values', messages: [aiMsg('Done')] }],
]);
await transport.emit(transport.nextBatch());

Looking for something specific?