Testing metadata
Test functions, flows, validation rules and formulas in your metadata package. Tests run on every check-only and real deploy, with every outside call mocked, and a failing test stops the deploy.
A metadata package can carry tests next to its source. They run when you deploy — check-only included — against the target org's metadata, before anything is written. If one fails, the deploy fails and nothing changes.
Where tests live
| Test | Path (under sourceDir) | Tests |
|---|---|---|
| Function | functions/<apiName>/tests/<name>.test.json | input + mocked secrets, fetch and salesos.* → output or error |
| Flow | tests/flows/<name>.test.json | inputs + screen answers + records → outputs, records written, path taken |
| Validation rule | tests/validation-rules/<name>.test.json | records → pass or fail, and the message |
| Formula | tests/formulas/<name>.test.json | records → the computed value |
Names are letters, digits, dots, dashes and underscores. Unknown properties are
rejected, like every other source file; the JSON Schema has functionTest,
flowTest, validationRuleTest and formulaTest under
$defs.
A test exercises the component in the package if it is there, otherwise the
one already in the org — so a package can carry only tests, and
salesos test --run-tests ALL re-checks what is deployed.
Function tests
The function runs in the same sandbox as production, with the same memory, CPU and output limits, but every call it makes out of the sandbox is answered from the test:
mocks.secrets— whatsecrets.get(name)returns. A name that isn't here fails the call, as a missing secret would.mocks.fetch[]—urlexact or a prefix ending in*, optionalmethod; answer withstatus,headersandbody(text) orjson, orerror. The URL must still be in the function'sallowedHosts, exactly as in production. A request with no mock fails: tests never reach the network.mocks.salesos[]—op(get,list,create,update,delete,graphql), optionalobjectandidto match, andresultorerror. Tests never touch CRM data; an unmocked call fails.expect— any ofstatus(SUCCEEDEDby default,FAILEDwhen you expect an error,TIMEOUT),output(deep equality),outputContains(subset),error(code—FUNCTION_ERRORfor an uncaught throw — and amessagesubstring),calls(host calls that must have happened, subset match) andlogs(a substring of the output log).
Function tests are what the sandbox can prove: the code compiles, runs inside its limits, and behaves as expected for the inputs you give it.
Flow tests
The flow runs on the same runtime as production — the same element
semantics, expressions, decisions, loops, faults and screen validation — but in
isolation: interviews aren't saved, record steps read the test's data
and record what they would write, custom actions and functions answer from
mocks, approvals and email are captured. Nothing is written or sent.
answers[]are submitted in order; each names the screen it answers. A screen that rejects the values fails the test with the field errors.dataholds the records Get Records and record lookups can see, per object (lead,task,custom:invoice…).mocks.actions/mocks.functionsanswer custom action and function steps by apiName ({ "output": {…} }or{ "error": { "code", "message" } }).expect—status(COMPLETEDby default,FAILED,PAUSED),outputs(subset of the output variables),path(element ids that ran, in order),notPath,records(writes that must happen:op,object,datasubset),recordWrites(exact count),emailsanderror.
Subflows resolve from the package, else the org's active version.
Validation rule and formula tests
A rule test evaluates the rule exactly as a save would (appliesTo, CHANGED
operators against previous, the rendered message). A formula test names a
FORMULA custom field (ENTITY:name) or a custom object's formula field
(custom:<object>.<field>). Up to 50 cases each.
Which tests run
POST /v1/metadata/deploy takes runTests:
runTests | Runs |
|---|---|
SPECIFIED (default) | The tests in the package |
ALL | The package's tests plus every test earlier deploys left in the org (the package's version of a file wins) |
NONE | Nothing — refused by the org policy below for changed functions and flows |
A successful real deploy keeps its tests in the org (deleting a function, flow
or rule removes its tests), and a full salesos metadata pull hands them back
as files. Results are on the deploy (GET /v1/metadata/deploys/:id →
testResults, and Settings → Developer → Deployments): one row per test
with status (passed, failed, error, skipped), time, where it came from
(package or org), why it failed, and details — the output and host calls of
a function, the path, outputs and record writes of a flow, each rule or formula
case.
Limits
Tests run one at a time. Each function or flow test gets at most 10 seconds (and never more than the function's own timeout); a deploy runs at most 100 tests within 120 seconds — tests left when the budget runs out are reported as not run, which fails the deploy.
Org policy
An org owner or admin can require tests: with Functions need tests and/or
Flows need tests on (Settings → Developer → Deployments, or PUT /v1/metadata/test-policy { "requireTestsFor": ["FUNCTION", "FLOW"] }), every
function or flow a deploy creates or changes needs at least one test in the run
that targets it, or the deploy fails on that component. Only a signed-in person
can change the policy — an API key or OAuth token gets 403 SESSION_REQUIRED —
so a CI credential can't switch off the rule that constrains it.
Metadata as code
Keep objects, fields, rules, workflows, actions, hooks, functions, pages, flows, profiles, reports and more in source control, test them, and deploy them from a developer org to production with the CLI or the metadata API.
Developer orgs and change sets
Free, separate organizations with sample data for building and testing against the platform, and change sets that promote what you built back to the org you came from.