On this pageTesting the Update Loop
Story
Story calls update with the Model and Messages you provide. Commands stay as data until the test supplies their result Messages. You can test a complete state transition without a DOM, a timer, or a running Effect.
The entire test is one story call. Start from a Model, send a Message, resolve any Commands, and assert on the next Model.
Import the steps you need from foldkit/story.
Top-level steps start the test, group reusable setup, send Messages, and inspect results:
story,steps,given,message,model,expectOutMessage, andexpectNoOutMessage.The
Commandnamespace handles pending Commands:resolve,resolveAll,resolveAllExact,expectHas,expectExact, andexpectNone.
Use named imports when the file contains only Story tests. If a file contains both Story and Scene tests, import the namespaces from foldkit so Story.given and Scene.given stay distinct.
A Command matcher accepts either a Definition or an instance. A Definition matches by name. An instance also checks structurally equal arguments.
Use FetchWeather when the test only cares that the Command was dispatched. Use FetchWeather({ zipCode: '90210' }) when the zip code is part of the contract.
Mount lifecycle is a Scene concern
Story does not render the view, so the OnMount lifecycle is not observable from a Story test. Tests that need to acknowledge mounts use Scene's Mount.resolve and the related steps; see the Scene page.
Here’s a test for the delayed reset from the Commands page. Clicking reset starts a one-second delay. When the delay completes, the count returns to zero.
Read it from top to bottom. The Model starts at 5. ClickedResetAfterDelay() returns DelayReset. The test resolves that Command with CompletedDelayReset(), which update handles by setting the count to 0.
Keep each Command result next to the Message that caused it. Command.resolve, Command.resolveAll, and Command.resolveAllExact let a longer test stay chronological:
The test does not run an HTTP request. It declares that FetchWeather succeeded, feeds the resulting Message through update, and checks the Model that the view will render.
Resolvers are a queue
Each resolveAll entry resolves one matching dispatch in declaration order. Three [FetchCount, message] entries resolve three FetchCount dispatches. For N identical responses, use Array.makeBy(n, () => [Definition, message]).
Unused resolvers can match later dispatches. A new resolver replaces any leftover resolver with the same Definition or instance shape.
Exact resolution
Use resolveAllExact when the resolver list is also a claim about which Commands were dispatched. It walks cascades like resolveAll, but every listed entry must match one dispatch within that call and no actual Command may remain unresolved. Repeated entries sharing a Definition consume repeated dispatches in declaration order. Supplied resolvers never carry forward.
Unresolved Commands
message throws if there are pending Commands from a previous step. Resolve all Commands before sending the next Message. story throws at the end if any Commands remain unresolved. Every Command your update function produces must be accounted for.
Use steps when several stories share the same setup or Message sequence. The group preserves the Model, Message, and OutMessage constraints of every step inside it, so the update passed to story still rejects a group built for a different program.
A group can contain any step accepted by story, including given, message, Model assertions, Command steps, OutMessage assertions, and another steps group. story runs the group in declaration order at the position where it appears.
Do not use Effect's flow to group Story steps. Message and OutMessage steps are typed data, not functions, and steps is the boundary that preserves their types. This does not replace flow for ordinary function composition elsewhere in an application.
Story tests the state machine. It does not run the Effect inside a Command.
To test that a Command’s Effect works correctly (for example, that an HTTP request parses the response right), test it separately with Effect.provide and a mock service layer:
The two tests cover different contracts. Story checks which Command update returns and what update does with its result Message. The Effect test checks the work the Command executes.