Expect Assertion API
@typecad/expect is a hardware test framework with a testing-library style API. You write assertions in TypeScript; the cuttlefish-test CLI turns them into firmware, runs them on the board, and reports the results back over serial. All the comparing and checking happens on your computer — the firmware running on the board only sends back the raw values it measured.
Install
@typecad/expect ships in the TypeCAD monorepo. In a workspace project it’s already available; otherwise install it alongside @typecad/cuttlefish:
npm install @typecad/expect @typecad/cuttlefishThe fluent chain
import { describe, done } from '@typecad/expect';
describe('Group name')
.it('test case name')
.expect(actualValue).matcher(expected)
.it('another case')
.expect(actualValue).matcher(expected);
done();describe(name: string): Suite— opens a group.Suite.it(name: string): Suite— opens a test case.Suite.expect(actual: number | (() => number)): Expectation— assert a numeric value.Suite.expectString(actual: string | (() => string)): StringExpectation— assert a string value.done(): void— required as the last statement; emits the end-of-suite sentinel and enters an idle loop so the host knows the firmware is finished.
Every matcher returns Suite, so you chain .it().expect().matcher() without semicolons between steps.
Numeric matchers (Expectation)
| Matcher | Asserts |
|---|---|
.toBe(expected) | actual === expected |
.toBeGreaterThan(n) | actual > n |
.toBeGreaterThanOrEqual(n) | actual >= n |
.toBeLessThan(n) | actual < n |
.toBeLessThanOrEqual(n) | actual <= n |
.toBeCloseTo(n, precision) | ` |
.toBeWithinRange(min, max) | actual >= min && actual <= max |
.toBeTruthy() | actual !== 0 |
.toBeFalsy() | actual === 0 |
.toNotBe(expected) | actual !== expected |
actual may be a value or a zero-arg function returning a value (() => A0.readAnalog()).
String matchers (StringExpectation)
| Matcher | Asserts |
|---|---|
.toBe(expected) | exact string equality |
.toContain(substring) | substring is present |
.toHaveLength(n) | string length equals n |
.toNotBe(expected) | not equal |
String expectations are for software string variables, not raw hardware reads.
Worked example
import { describe, done } from '@typecad/expect';
import { A0, A1 } from '@typecad/board';
describe('A0 analog read')
.it('reads a value in valid ADC range')
.expect(A0.readAnalog()).toBeWithinRange(0, 1023)
.it('reads less than mid-scale when grounded')
.expect(A0.readAnalog()).toBeLessThan(512);
describe('A1 analog read')
.it('returns a non-negative value')
.expect(A1.readAnalog()).toBeGreaterThanOrEqual(0)
.it('is within 10-bit ADC range')
.expect(A1.readAnalog()).toBeLessThanOrEqual(1023);
done();You can also assert on plain TypeScript values (enums, arrays, destructuring, function results), not just hardware reads:
import { describe, done } from '@typecad/expect';
enum Mode { Idle = 0, Active = 1 }
const baseline: ReadonlyArray<number> = [1, 2, 3, 4];
function baselineSum(): number {
let total = 0;
for (const v of baseline) total += v;
return total;
}
describe('Logic correctness')
.it('sums readonly arrays correctly')
.expect(baselineSum()).toBe(10)
.it('returns enum-backed values correctly')
.expect(chooseMode(true)).toBe(1);
done();The cuttlefish-test CLI
cuttlefish-test [options] [files...]| Flag | Short | Default | Description |
|---|---|---|---|
--port <port> | -p | from config | Serial port (e.g. COM4, /dev/ttyACM0) |
--board <pkg> | -b | from config | Board package override |
--build-target <fqbn> | from config | Framework-specific build target / FQBN override | |
--baud <rate> | 115200 | Serial baud rate | |
--timeout <ms> | -t | 30000 | Serial read timeout in milliseconds |
--include <glob> | -i | from config | Test file glob pattern (repeatable) |
--exclude <glob> | -x | from config | Test file glob pattern to skip (repeatable) |
--verbose | -v | off | Show raw serial output and per-assertion detail |
--help | -h | Print help and exit |
Positional file arguments are treated as --include patterns (overriding config). Defaults for board, target, and buildTarget fall back to your cuttlefish.config.ts root fields when the test section doesn’t set them — so only --port is typically required on the command line.
Test configuration
Add a test section to cuttlefish.config.ts:
test: {
include: ['tests/**/*.test.ts'], // required — glob patterns for test files
exclude: ['tests/slow/**'], // optional — patterns to skip after discovery
port: 'COM4', // required — serial port
baudRate: 115200, // required — default 115200
timeout: 30000, // required — default 30000 ms
serialOpenDelay: 500, // optional — delay before opening serial (after reset); default 500
resetAfterOpen: true, // optional — toggle ESP32-style DTR/RTS reset after opening
buildTarget: 'arduino:avr:uno', // optional — overrides the root frameworkData.buildTarget
board: '@typecad/board-arduino-uno', // optional — overrides the root board
// verbose: not settable from config; use the --verbose CLI flag
}Merge precedence: defaults → config file → CLI flags (each level overrides the previous, per field).
Serial protocol
While running, the board and your computer exchange short text lines to report each test’s outcome.
⚙️ Advanced details — the serial protocol
The firmware emits structured lines the host parses:
[TC:SUITE_START][TC:DESCRIBE:<name>][TC:IT:<name>][TC:EXPECT:<matcher>:<expected>:<actual>][TC:SUITE_END]
All non-[TC: lines are treated as debug output and shown with --verbose.
Per-target skip directives
File-level comments control which targets run a test:
// @typecad-skip-target esp32: ESP32 lacks the AVR watchdog API.
// @typecad-only-target avr,megaavr: uses AVR watchdog registers.Targets are matched against: your target, the FQBN parts of frameworkData.buildTarget, the full FQBN, and the board package name. Skipped files appear in the output (vitest-style ↓ tests/x.test.ts (skipped)); run with --verbose to print the reason.
Limitations
- No callback suites — groups and cases are defined by fluent chaining, not
describe("name", () => { ... }). - No async tests — timing is implicit (the board executes sequentially; the host waits on serial).
- Sequential execution — all
describeblocks in a file run once, in order, insidesetup(). NobeforeEach/afterEach. - One file per upload — each test file produces one sketch and one upload cycle.
- Numeric types only for hardware values — Cuttlefish maps hardware reads to
int/float. UseexpectStringonly for software string variables.
Back to Testing & Diagnostics.
On This Page