Testing
@threadplane/ag-ui gives you two test doubles, smallest scope first:
provideFakeAgent()โ a one-call fake backend that runs the real adapter pipeline and streams canned tokens. No server, no LLM.mockAgent()(from@threadplane/chat) โ a writable-signal mock for component/unit tests. The ag-ui agent is the neutralAgentcontract, so there is no ag-ui-specific mock โ use the neutral one directly.
See Choosing an adapter โ Testing for when to use each test double.
#Fake backend: provideFakeAgent()
provideFakeAgent({ tokens, reasoningTokens, delayMs }) wires a fake backend into Angular DI in one call. It exercises the real adapter pipeline โ injectAgent(), status transitions, message accumulation โ but the wire events are canned, so there's no server and no LLM. Use it for adapter-integration tests, in-browser demos, and offline development.
It drops in exactly where provideAgent() would go โ the call shape matches the LangGraph adapter:
The component is byte-identical to production โ only the provider changes. FakeAgentConfig ({ tokens?, reasoningTokens?, delayMs? }) lives in @threadplane/chat/testing:
| Option | Type | Notes |
|---|---|---|
tokens | string[] | Emitted as streamed text deltas, in order. |
reasoningTokens | string[] | Emitted before text deltas to exercise reasoning UI. |
delayMs | number | Delay between streamed events. |
For the underlying FakeAgent class and its canned event sequence, see Fake Agent.
#A runnable spec
Set delayMs: 0 to collapse the inter-token delay, drive submit(), then assert the streamed assistant content shows up on agent.messages(). Awaiting submit() is what guarantees the run finished โ runAgent() resolves once the canned stream completes:
injectAgent() needs an injection context, so resolve it through TestBed.runInInjectionContext(). The RUN_FINISHED event has reduced by the time the awaited submit() resolves, so no fake-timer flushing is required.
#Contract mock: mockAgent()
For component and unit tests where you don't need a streaming pipeline at all, use the neutral mockAgent(initial) from @threadplane/chat. It returns an Agent whose state surface is exposed as writable signals โ set state directly and assert your component reacts. Nothing is real.
The ag-ui adapter exposes exactly the neutral Agent contract, so there is no ag-ui-specific mock โ mockAgent() is the right tool.
#Testing tool calls, state, and custom events
FakeAgent only scripts RUN_*, REASONING_MESSAGE_*, and TEXT_MESSAGE_* events โ it never emits TOOL_CALL_*, STATE_SNAPSHOT/STATE_DELTA, or CUSTOM. To exercise the reducer's headline non-text features โ tool-call rendering, shared state, citations, custom events โ script your own AbstractAgent and feed it through toAgent(). The adapter reduces your scripted events into toolCalls(), state(), and customEvents() exactly as it would real wire events.
customEvents() is the AG-UI-specific signal โ toAgent() returns an AgUiAgent, so it's reachable directly here without a cast. A CUSTOM event named on_interrupt would instead populate agent.interrupt(); see the Interrupts guide.