Skip to content

Verb tests

A Verb test file holds the cases a Verb's Function must pass before any Change to it merges.

A file named [Noun].[verb].test.ts holds the cases a Verb's Function must pass before any Change to it merges.

  • nouns/
  • Lease.renew.ts
  • Lease.renew.test.tsits cases

Reference

TypeScript with test, expect and run from startup-builder/test. The same runner runs Workflow test blocks.

ExportTypeHolds
test(name, fn)functionOne case
run(input, stubs)functionRuns the Function with stubbed calls and returns { result, calls }
expect(value)functionAssertions on the result and the calls

Examples

Minimal

nouns/Lease.renew.test.ts
import { expect, run, test } from 'startup-builder/test'

test('renews for a year', async () => {
  const { result } = await run({ lease: 'lease_1', months: 12 }, { Lease: { get: () => ({ $id: 'lease_1', endsOn: '2026-12-31' }) } })
  expect(result.endsOn).toBe('2027-12-31')
})

Leaseline

Leaseline declares a Lease Noun and caps each rent increase.

nouns/Lease.renew.test.ts
import { expect, run, test } from 'startup-builder/test'

const lease = { $id: 'lease_1', endsOn: '2026-12-31', rent: { amount: 1800, currency: 'USD' } }

test('a 3% increase raises the rent', async () => {
  const { result, calls } = await run({ lease: 'lease_1', months: 12, increase: 0.03 }, { Lease: { get: () => lease } })
  expect(result.rent.amount).toBe(1854)
  expect(calls).toEqual(['Lease.update'])
})

What it builds

The SoftwareBuilder runs every case on each preview.

  • a check result per case on the Change
  • Change.merge refuses while a case fails
  • npx startup-builder dev reruns the cases on every save

Good to know

  • Calls to outside Integrations are stubbed, so a case never moves money or sends a Message.
  • An agentic Function's cases replay recorded model output, so CI makes no model call.