Skip to content

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.

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.

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.

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.

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.

Manual sends, request run, collection run, and the TUI Runner use this order:

  1. Resolve folder overrides.
  2. Resolve the environment and RunScope.
  3. Substitute request values and body templates once, after source preflight.
  4. Run collection, folder, and request pre blocks.
  5. Send HTTP.
  6. Evaluate captures.
  7. Run collection, outermost-to-nearest folder, and request post blocks.
  8. Evaluate declarative assertions.
  9. 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.

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 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.

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.

Noodle Tests editor checking a users response with test and expect assertions

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.

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.