Scripted tests
Use declarative assert
for simple response contracts and a request-level tests string for conditions,
loops, or related JSON checks:
tests: | test("user is active", () => { expect(noodle.response.json().status).toBe("active") }) test("successful response has a user", () => { if (noodle.response.status < 400) { const user = noodle.response.json().user expect(user.id).toBeDefined() expect(user.roles).toContain("member") expect(user.profile).toEqual({ active: true }) } })For complete examples with expected results, see the Testing APIs cookbook.
Request schema and test declarations
Section titled “Request schema and test declarations”test(name, callback) invokes callbacks immediately and awaits returned Promises
or thenables before finalizing results. Results retain declaration order, including
duplicate names. Names must be non-empty strings. A failed matcher or callback
fails that test and later tests continue. A top-level error stops the script but
preserves completed results. Empty source is a successful no-op. tests must be
an inline string or a collection-relative ./path/to/file.js reference.
See external JavaScript files
for preflight and path limits. Modules are unsupported.
Source is never variable-substituted. Network APIs and state mutations are unavailable.
Omit tests to keep the existing request behavior and output; a non-string
tests value is invalid.
Canonical YAML serialization preserves source whitespace in a literal block.
test() and expect() are global only in this phase. Continue to use the
noodle. prefix for request, response, environment, and other Noodle APIs.
Matcher reference
Section titled “Matcher reference”Every matcher supports .not, for example expect(value).not.toBeNull().
Type errors fail even with .not:
| Matcher | Behavior |
|---|---|
toBe(expected) |
Object.is, including NaN and signed zero; objects compare by identity |
toEqual(expected) |
Typed deep JSON equality; array order matters, object key order does not |
toBeTruthy() |
Value is truthy in JavaScript |
toBeFalsy() |
Value is falsy in JavaScript |
toBeDefined() |
Value is not undefined; JSON null is defined |
toBeNull() |
Value is exactly null |
toContain(expected) |
String substring or deep-equal JSON array element |
toMatch(pattern) |
String regex without flags, or RegExp with its explicit flags; leaves lastIndex unchanged |
toBeGreaterThan(expected) |
Finite number strictly greater than expected |
toBeGreaterThanOrEqual(expected) |
Finite number greater than or equal to expected |
toBeLessThan(expected) |
Finite number strictly less than expected |
toBeLessThanOrEqual(expected) |
Finite number less than or equal to expected |
toMatchSchema(schema) |
JSON Schema draft-07 validation with standard formats and local references |
toHaveProperty(key, expected?) |
Own literal string property, optionally compared with deep equality; dots do not traverse |
toHaveLength(length) |
String or array length; expected length is a non-negative safe integer |
toBeTypeOf(type) |
JavaScript typeof, including object for null; rejects invalid type names |
toMatchObject(partial) |
Recursive object subset; arrays compare completely and in order |
Numeric comparisons require finite numbers on both sides, without coercion. String containment requires a string substring; array containment compares JSON-compatible elements deeply. For example:
expect([{ id: 1 }, { id: 2 }]).toContain({ id: 2 })expect("ACTIVE").toMatch(/^active$/i)expect("ACTIVE").not.toMatch("^active$")expect(null).toBeDefined()expect(undefined).not.toBeDefined()Invalid regex patterns fail the current test.
Cyclic values, unsafe prototypes/keys, accessors, non-transferable values, and
oversized matcher values are rejected. Deep equality requires JSON-compatible
values; scalar undefined and non-finite numbers remain available to identity
and truthiness checks. No other matchers are supported.
Inherited scripts and tests
Section titled “Inherited scripts and tests”Declare scripts.pre, scripts.post, and tests as inline JavaScript or
external file references
in collection settings.yml, nested folder.yml, or request YAML.
- Pre: collection → outermost folder → nearest folder → request.
- Post, after captures: collection → outermost folder → nearest folder → request.
- Tests, after assertions: collection → outermost folder → nearest folder → request.
All three phases use the same scope order. The most specific successful post write wins, including persisted writes. Versions 0.9.4 and 0.9.5 ran post blocks in reverse order; update collection or folder post blocks that depend on values produced by request post blocks when upgrading.
Collection and folder blocks run once per request,
including each dataset row. Root folder.yml is ignored. An empty block is a
no-op and does not disable inherited blocks.
Every block has a fresh QuickJS invocation. JavaScript locals are isolated;
successful RunScope writes are visible to later blocks. A pre failure stops
remaining pre blocks and HTTP. A post failure rolls back only that block and
continues later posts, assertions, and tests. A top-level test error stops only
its block. Successful persistence intents keep execution order; collection
runs and F5 suppress persistence as before. Saved noodle.runRequest() calls
use the same inheritance and cycle protections.
Executed blocks identify their phase, scope, scope ID, and source kind.
source.path is the declaring YAML path; source.scopeId identifies the
collection, folder path, or request ID. source.sourceKind is inline or
external; external blocks add the collection-relative source.sourcePath.
Test groups retain ordered invocations, including blocks declaring zero tests,
and errors for multiple block errors; legacy error remains the first error.
CLI, Results, and history show origins and bounded, redacted diagnostics and
logs without retaining source code or RunScope values.
Inherited blocks share a 64 KiB console-text budget and a 256 KiB test-record
budget per request. Logs become [TRUNCATED] at the limit; exhausted test
records produce a test script error, and later blocks are still invoked.
JSON Schema validation
Section titled “JSON Schema validation”test("valid user", () => { expect(noodle.response.json()).toMatchSchema({ type: "object", required: ["id", "email"], properties: { id: { type: "integer" }, email: { type: "string", format: "email" } } })})toMatchSchema(schema) uses Ajv 8 and ajv-formats inside QuickJS. Compilation
and validation share the invocation’s CPU, memory, and stack limits. Schemas
use draft-07 by default; explicit other dialects fail. Boolean schemas and
local fragment references such as #/definitions/user are supported. Remote
and file references, async schemas, and custom validators are unavailable.
Validation never coerces types, applies defaults, or removes properties.
.not.toMatchSchema(schema) passes only when valid schema validation fails.
An invalid schema fails even under .not. Failure details show the first
data path, failed keyword, and message, bounded and redacted like other tests.
Execution order and read-only access
Section titled “Execution order and read-only access”Manual sends, request run, collection run, and the TUI Runner use this order:
- Resolve folder overrides.
- Resolve the environment and RunScope.
- Substitute request values and body templates once, after source preflight.
- Run collection, folder, and request pre blocks.
- Send HTTP.
- Evaluate captures.
- Run collection, outermost-to-nearest folder, and request post blocks.
- Evaluate declarative assertions.
- Run scripted tests.
Tests run whenever a response exists, including after HTTP, capture, post, or
assertion failures. Pre and transport failures leave tests unevaluated. Use
scripts.post for mutations. Tests read the final prepared request, response,
environment, RunScope, and applicable final-URL cookies through noodle.*;
crypto, random, time, and captured console helpers remain available. State
mutators fail, response JSON is frozen, and tests cannot persist values.
Execution and limits
Section titled “Execution and limits”The existing QuickJS limits apply, including 500 ms of VM execution across resumptions, a 30-second wall deadline, 32 MiB runtime memory, 256 KiB source/matcher values and retained test records, depth 32 JSON values, and the lazy 5 MiB response text limit. Unresolved Promises and resource limits stop the group while keeping completed results.
Results and failures
Section titled “Results and failures”Results add tests: { evaluated, results, logs, error?, errors? }. Each ordered result
contains name, passed, message, and durationMs; error describes a
separate top-level script failure. Known secrets are redacted from names,
messages, errors, and logs, including before error cleanup and truncation.
Oversized diagnostics are replaced with [TRUNCATED] instead of retaining a
partial secret. Failed tests or a script error add failure category
test and make automation exit nonzero. A request succeeds only when HTTP
status is below 400, all captures and pre/post scripts succeed, all declarative
assertions pass, and all scripted tests pass. Returning early from a conditional
test does not override an HTTP or other execution failure.
Collection summaries add testPasses, testFailures, and testScriptErrors when tests are present; fail-fast waits
for all diagnostics. Requests without tests keep their previous output.
Author tests in the TUI
Section titled “Author tests in the TUI”Reveal Tests from a request or folder + menu, or open
F4 → Collection → Scripts → Collection Tests. Choose inline JavaScript or
a collection-relative .js file. The editor completes test, expect,
matchers, and read-only phase APIs, with syntax checks and advisory semantic
diagnostics. See script authoring
for shortcuts and save behavior.

View results
Section titled “View results”Human output shows counts and concise failures. JSON includes structured results and redacted logs. TUI Results shows expandable test outcomes; Console shows log messages in manual sends, Runner details, and timeline history. Manual history retains bounded, redacted diagnostics, with the existing 10,000-byte diagnostic text/log limits; it excludes test source and runtime values. Foreign script importer conversion remains unavailable.
See Automation for running requests and collections, and Scripting for request preparation and response mutations.
Async callbacks
Section titled “Async callbacks”tests: |- test("has a user ID", async () => { const user = await Promise.resolve(noodle.response.json()); expect(user.id).toBeGreaterThan(0); });Synchronous callbacks still execute immediately. Async callbacks settle before results are returned, with results kept in declaration order. Use pre/post scripts for HTTP calls and mutations; tests expose neither network API.