Mock nodes and test configs

Replace any node or group with canned data. This unlocks top-down building, fast iteration, and cheap tests.

This is one of the most important features in Weft. Almost nobody uses it on day one, and almost everyone wishes they had started with it by week two.

The idea is simple: any node or group can be replaced with "return this data instead of running for real". The replacement is typed against the real node's output schema, so mocks cannot silently drift from the thing they are mocking. Replace one LLM call, replace a whole enrichment group, replace a database query, the mechanism is the same.

The feature lives in the Builder toolbar top-right under Tests. When a test config is active, the button turns amber and says "Test ON". Every run uses the mocks until you turn it off.

Screenshot needed
The Tests button in the top-right toolbar: in the off state (gray) and in the on state (amber pill saying "Test ON").

Why this matters so much

AI systems are slow and expensive to test. Every LLM call is a few seconds and a few cents. Every external API touches real data. Every human-in-the-loop form requires, well, a human. If you had to run the full thing every time you changed anything, development would crawl and your bill would spike.

Without a mocking story, people default to the worst workflow: they tweak a prompt, click Run, wait for the LLM, realize the prompt is still wrong, tweak it again, click Run. Every iteration costs. Nobody iterates enough. The result is "prompts that work most of the time" instead of "prompts you actually tested on edge cases".

With mocks, you freeze the slow or expensive pieces behind canned data and iterate on the piece you actually care about. Two clicks, one save, 100x faster loop.

How it works under the hood

A test config is a named set of mock values, stored on the project. Each mock is a nodeId -> { portName: value } map. When you activate a config, the executor receives it alongside the project definition. Before running each node, it checks: is this node in the mocks map?

  • Node mock. The node is skipped entirely. Its code does not run. Its outputs are emitted directly from the mock values, after being sanitized against the real port schema: missing mock ports become null, extra mock ports are dropped. Downstream nodes see what they would have seen from a real run.
  • Group mock. Same thing at the group boundary. The entire group body is skipped. The group's output ports emit the mock values as if the inner nodes had run. Child nodes inside the mocked group are marked as "skipped by mock" in the execution view.

The mock editor uses the real port schema. When you add a node to mock, each of its output ports appears with the right editor (checkbox for Boolean, number input for Number, JSON textarea for List and Dict and JsonDict, plain text otherwise). That keeps you close to the right shape without typing raw JSON for everything. It is not a strict type check at save time, a mock that claims a String port returns a number will still save and will still be emitted, so if downstream breaks, look at your mock values first.

Top-down building

This is the workflow that makes big projects tractable. Read it carefully, it is the thing.

  1. Sketch the top-level architecture as stub groups with typed interfaces. Define what each group takes in, what it produces, and wire a tiny placeholder body that satisfies the output ports. Every group's interface compiles, even though the body is fake. Ask Tangle for a "stub scaffold" and it will write these for you.
  2. Mock every group with plausible output values. Open the Tests modal, create a new config, add each group with a made-up but realistic output. Activate the config.
  3. Click Run. The whole project executes instantly, start to finish. Every group returns its mock (the stub body never runs because the group is mocked). The final output is produced. You can already see what the system is going to look like end to end.
  4. Pick one group to build for real. Work inside it, test it in isolation, verify it does what you want. While you iterate, the rest of the project is still mocked, so your end-to-end run stays fast and free.
  5. Remove the mock for that group. Run the project. That group now runs for real, the others are still mocked. Verify the real group's output matches the shape the downstream groups expect. Fix any gaps.
  6. Repeat for the next group. Each time you replace a mock with the real thing, you have been validating the integration as you went. By the time every group is real, every piece has been running against the others from day one.

No big-bang integration at the end. No "it works in isolation but the pieces do not fit". No weeks of wiring before you see anything run. The project is always runnable, always produces something, always makes progress visible.

This workflow leans on Weft being typed. Groups have typed interfaces, so a stubbed group slots into the same downstream wiring as the real implementation. Mocks plug into those interfaces by port name, so swapping a mock for the real body does not require touching any downstream connection.

Creating a test config

  1. Click the Tests button in the top-right toolbar. The test config modal opens.
  2. Click New test config. Give it a name like "happy path" or "error handling".
  3. Click Add node to mock. Pick any node or group from your project. Its output ports appear with default values.
  4. Fill in the mock values. Each port renders the right editor based on its type (checkbox for Boolean, textarea for JSON, number field for Number, etc.).
  5. Save. The config is stored on the project.
  6. Click a config in the list to activate it. The toolbar button turns amber. Every run now uses the mocks.
Screenshot needed
The test config modal in edit mode, showing a mocked LLM node with port values filled in.

Multiple configs per project

You can have several test configs on the same project and switch between them with one click. Common patterns:

  • Happy path. Everything returns plausible, well-formed data. Use this to verify the end-to-end shape of your project.
  • Error path. One node returns something pathological (empty list, null where you expected a value, malformed JSON). Use this to check your error handling.
  • Real sample. Mock one LLM call with an actual response you copied from a real run. Use this to iterate on downstream logic without paying for the LLM again.
  • Demo data. Mock everything with polished, impressive-looking outputs for a live demo where you do not want to depend on real APIs or real latency.

Switching between configs takes one click. The whole project re-runs with the new mocks.

What you can mock

Anything that produces data. In practice:

  • LLM calls. The most common mock. Replace a real inference with a canned response.
  • External API calls. Replace a web search, an enrichment service, a scraper, a vector query.
  • Whole groups. Replace a 20-node enrichment pipeline with one canned lead record. Essential for top-down building.
  • Code nodes. Replace an ExecPython with its expected output while you work on the node that consumes it.

Triggers and infrastructure nodes are not mockable from the Tests modal. They have their own lifecycle (they are already "canned" in a sense: they run before and outside the execution). You mock the downstream nodes that consume their output instead.

Persistence and selection

Your selected test config persists across reloads. If you activate "happy path" and close the tab, opening the project tomorrow still has "happy path" active. The amber "Test ON" button is there to remind you. This is saving you from the worst class of bug, which is "I thought I was running against real data but I was running against mocks".

To turn mocking off, click the amber button and press Deactivate. The button goes gray, runs use real data again.

Ask Tangle to set it up

You do not have to fill in mocks by hand. Ask Tangle: "set up a test config that mocks the LLM to return a one-line summary, and mocks the enrichment group to return Alice at Acme". Tangle picks the right nodes, generates type-valid mock values, and creates the config. Review, activate, iterate.

What's next

  • Groups: the natural unit for top-down building.
  • Inspect a past run: see how mocked vs real nodes appear in the run view.
  • Types: the thing that keeps mocks honest.