ng-mcp-ui/testing
MockAdaptor and provideMockMcpUi, a pure provider override that mirrors provideMcpUi with no real host.
ng-mcp-ui/testing gives a unit test and Storybook a provider override that mirrors
provideMcpUi(). There is no window.mcpUi, no postMessage, and no real host. The widget resolves
its adaptor through the same MCP_ADAPTOR token that it always uses.
Full pages: provideMockMcpUi and
MockAdaptor.
provideMockMcpUi
import { provideMockMcpUi } from "ng-mcp-ui/testing";
const { providers, adaptor } = provideMockMcpUi({
hostContext: { theme: "dark" },
toolResponses: { cast_vote: { structuredContent: { /* … */ } } },
});
TestBed.configureTestingModule({ providers: [providers] });
| Field | Purpose |
|---|---|
providers |
Pass to TestBed.configureTestingModule, or to a Storybook decorator. |
adaptor |
The MockAdaptor instance. Use it to drive the host and to read what the widget sent. |
| Option | Purpose |
|---|---|
hostContext |
Seed values for any subset of the host-context keys: theme, display mode, tool input and output, view state, and the others. |
toolResponses |
A canned result for each tool name. injectCallTool receives it. |
serverUrl |
The value bound to MCP_SERVER_URL. The default is an empty string. |
A callTool for a name that toolResponses does not list resolves with an empty success response.
Therefore a wrapper still reaches the success state, and you list only the tools that a test asserts
on.
A canned response with resultType: "input_required" is rejected: the callTool promise throws
with an explanatory message. No host surfaces a multi-round trip to a widget, so a tool that a view
calls must complete in one round. Keep an elicitation flow on a
chat-path tool, and test that flow against the server instead.
MockAdaptor
// push host state after boot, exactly as a real host does
adaptor.pushHostContext("toolOutput", { question: "Lunch?", options: ["Ramen", "Tacos"] });
// assert what the widget sent
expect(adaptor.calls).toContainEqual({
method: "callTool",
args: ["cast_vote", { pollId: "p1", option: "Ramen" }],
});
pushHostContext(key, value) drives any host-context key. It fires the subscribers of that key,
therefore every matching signal in the widget changes.
calls is the log of each call that the widget made to the host: a tool call, a size request, a
follow-up message, and the others. Thus you assert an interaction with no spy.
Each entry has the shape { method, args }. method is a bridge method name, or
"registerViewTool:dispose" for a teardown. args is the argument list of that call, in order.
clearCalls() empties the log between assertions.
Notes
- The mock mirrors
provideMcpUi(), and that includes zoneless change detection. After an async interaction, preferawait fixture.whenStable()overdetectChanges(). - Everything goes through
MCP_ADAPTOR, therefore you stub nothing onwindow, and the same override works in Storybook with no change. See testing widgets for the decorator, andexamples/storybookfor a runnable example. - The providers also work in a bare Angular injector, with no
TestBed.
See testing widgets for a full example.