Octov0.11.7
Getting Started

Unit-Testing a Flow

Put a flow under test with dolphin, and make testing part of the edit loop.

Invoking a flow by hand tells you it works right now. A test tells you it still works after the next edit, and writing one is the other half of the edit loop: change the flow, run the suite, keep going. This page puts the flow from Your First Flow under test.

You need bin/octo and bin/dolphin from Installation.

A suite is a YAML file beside the flow

A suite is named after the flow file it tests, so hello-invoke.yaml is paired with hello-invoke_test.yaml. This one ships beside the sample:

samples/hello-invoke_test.yaml
flow: greet

cases:
  - name: it greets the name in the body
    input:
      data: { name: Ada }
    expect:
      body: { greeting: "hello, Ada!" }

flow names the flow under test. Each case gives an input and says what it expects: input.data is the body the flow is called with, and expect.body is what the resulting body has to contain.

Run it by pointing at the flow file, not at the test file:

export OCTO_PATH=$PWD/bin/octo
bin/dolphin test samples/hello-invoke.yaml
ok   samples/hello-invoke_test.yaml  (greet)  32ms

1 passed, 0 failed, 0 errored, 0 skipped  (32ms)

dolphin runs your tests by running the real octo, so OCTO_PATH tells it which binary to drive. It then finds the _test.yaml beside the flow on its own, the way Go pairs orders.go with orders_test.go, and octo skips those files when it loads a directory.

Sources do not run under a test. Nothing binds a port and no schedule fires; only the named flow runs, on the message the case builds. So a flow that reads vars.method sees it only because the case set it.

Watch it fail

A test you have never seen fail is a test you should not trust. Change the expected greeting to hello, Grace! and run it again:

FAIL samples/hello-invoke_test.yaml  (greet)  25ms
  --- FAIL: it greets the name in the body (25ms)
        expect.body:
              want: {"greeting":"hello, Grace!"}
              got:  {"greeting":"hello, Ada!"}
        reproduce: bin/octo invoke --config samples/hello-invoke.yaml --flow greet …

0 passed, 1 failed, 0 errored, 0 skipped  (25ms)

The failure names the field, both values, and a reproduce command that runs that one case again outside dolphin, with the same debug config the runner used. Put it back to Ada and the suite goes green again.

Assert on what happened inside

expect looks at the message that came out. spies look at what the flow did on the way, which is how you catch a flow that returns the right answer for the wrong reason:

samples/hello-invoke_test.yaml
cases:
  - name: it greets the name in the body
    input:
      data: { name: Ada }
    expect:
      body: { greeting: "hello, Ada!" }
    spies:
      greet.build-greeting:
        count: 1

greet.build-greeting is a block address: the flow, then the block's name. count: 1 says that block was reached exactly once. A block nested in a composite is addressed through the slot it lives in, as in orders.route-by-method[0].lookup-order. See the address grammar.

Where this goes next

Nothing above is mocked, because nothing in this flow calls the world. The moment a flow does, you stand in for the block that makes the call and let everything around it run for real. That is the one technique that turns an untestable flow into a testable one, and Your First Test walks it end to end in about ten minutes on a flow that charges a payment API.

From there, Testing covers the suite format in full, running suites in CI, and writing them in the editor against a canvas instead of a file.

On this page