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 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.
For component and unit tests where you don't need a streaming pipeline at all, mockLangGraphAgent(initial) returns a LangGraphAgent whose state surface is exposed as writable signals. Set state directly and assert your component reacts — nothing is real.
import { mockLangGraphAgent } from '@threadplane/langgraph';const m = mockLangGraphAgent({ status: 'running' });m.messages.set([{ role: 'assistant', content: 'Hello!' }]);expect(m.messages()[0].content).toBe('Hello!');expect(m.status()).toBe('running');
mockLangGraphAgent() extends the neutral mockAgent() from @threadplane/chat — it supplies the neutral Agent-contract writable signals (messages, status, isLoading, error, interrupt, …) plus submit/stop call tracking, then layers the LangGraph-specific signals (langGraphMessages, branch, queue, …) on top.
Lead with provideFakeAgent() for the simple case. Reach for MockAgentTransport only when you need to hand-script exact wire event sequences — tool calls, interrupts, or multi-batch streaming lifecycles that the canned-token fake can't express.
✓No flaky tests
MockAgentTransport eliminates network dependencies, timing issues, and server state. Every test run produces identical results.
Before testing the Angular side, make sure your agent logic is correct. LangGraph agents are plain Python functions — test them directly with pytest.
import pytestfrom langchain_core.messages import HumanMessagefrom my_agent.agent import graph@pytest.mark.asyncioasync def test_agent_responds(): result = await graph.ainvoke( {"messages": [HumanMessage(content="Hello")]}, config={"configurable": {"thread_id": "test_1"}}, ) assert len(result["messages"]) >= 2 assert result["messages"][-1].type == "ai"@pytest.mark.asyncioasync def test_agent_uses_tools(): result = await graph.ainvoke( {"messages": [HumanMessage(content="Search for LangGraph docs")]}, config={"configurable": {"thread_id": "test_2"}}, ) # Verify the agent called the search tool tool_messages = [m for m in result["messages"] if m.type == "tool"] assert len(tool_messages) > 0
iAgent tests are fast
With MemorySaver and a mocked LLM, agent tests run in milliseconds. Use langchain_core.language_models.FakeListChatModel to remove the LLM dependency entirely.
On the Angular side, MockAgentTransport replaces the real HTTP transport. Register it through provideAgent({ transport }) in TestBed's providers array, then call injectAgent() inside TestBed.runInInjectionContext.
Pass event batches to the constructor for sequential playback. Each call to nextBatch() returns one batch; emit that batch to advance what the component sees.
const transport = new MockAgentTransport([ // Batch 1: Agent starts thinking [{ type: 'values', messages: [{ role: 'assistant', content: 'Analyzing...' }] }], // Batch 2: Agent finishes [{ type: 'values', messages: [{ role: 'assistant', content: 'Here is your answer.' }] }],]);TestBed.configureTestingModule({ providers: [ provideAgent({ apiUrl: '', assistantId: 'test_agent', transport }), ],});TestBed.runInInjectionContext(() => { const chat = injectAgent(); chat.submit({ message: 'Explain signals' }); // Step through each batch transport.emit(transport.nextBatch()); expect(chat.messages()[0].content).toBe('Analyzing...'); transport.emit(transport.nextBatch()); expect(chat.messages()[0].content).toBe('Here is your answer.');});
The most common test pattern verifies the full submit-to-idle lifecycle: submit sets the agent running, values arrive, and the status settles back to idle.
Script an interrupt event to test human-in-the-loop flows. Verify the interrupt signal surfaces the payload, then resume and confirm the agent continues.
import { TestBed } from '@angular/core/testing';import { MockAgentTransport, provideAgent, injectAgent } from '@threadplane/langgraph';describe('interrupt handling', () => { it('should surface interrupt and resume on approval', () => { const transport = new MockAgentTransport(); TestBed.configureTestingModule({ providers: [ provideAgent({ apiUrl: '', assistantId: 'approval_agent', transport }), ], }); TestBed.runInInjectionContext(() => { const chat = injectAgent(); // Agent hits an interrupt transport.emit([ { type: 'interrupt', value: { action: 'delete_account', risk: 'high' }, }, ]); // Verify interrupt signal expect(chat.interrupt()).toBeDefined(); expect(chat.interrupt()?.value.action).toBe('delete_account'); expect(chat.interrupt()?.value.risk).toBe('high'); // User approves — resume the agent chat.submit({ resume: { approved: true } }); // Agent continues after approval transport.emit([ { type: 'values', messages: [{ role: 'assistant', content: 'Account deleted.' }], }, ]); expect(chat.interrupt()).toBeUndefined(); expect(chat.messages()[0].content).toBe('Account deleted.'); }); });});
Make sure @threadplane/langgraph is available in your test environment. MockAgentTransport ships with the main package — no extra install needed.
2
Create the transport
Instantiate MockAgentTransport with optional pre-scripted batches for sequential playback, or leave it empty for imperative emit() calls.
3
Wrap in injection context
Call TestBed.runInInjectionContext(() => { ... }) so injectAgent() can access Angular's injector for signal creation and cleanup.
4
Configure the provider
Pass the transport into provideAgent({ ..., transport }) in TestBed's providers array. All other options (assistantId, threadId, onThreadId) work identically to production code.
5
Script events
Use transport.emit() for ad-hoc events, transport.nextBatch() for pre-scripted sequences, or transport.emitError() for failure scenarios.
6
Assert signal values
Read signals like chat.messages(), chat.status(), chat.interrupt(), and chat.error() to verify your component reacts correctly.
For end-to-end confidence, run tests against a real LangGraph dev server. The LangGraph CLI starts a local server that your tests can hit directly.
# Start the dev serverlanggraph dev --config langgraph.json# Run Angular tests against it (no MockAgentTransport needed)ng test --watch=false
!Integration tests are slow
Integration tests hit a real server and (potentially) a real LLM. Reserve them for CI pipelines or pre-release smoke tests. Use MockAgentTransport for the vast majority of your test suite — it runs in milliseconds with zero external dependencies.