Docs/Run and test

Run and test

Execute Apex, build test data, diagnose failures, and measure test quality.

HTTP Mocking

There are two separate contexts for HTTP in Nimbus, and they work differently:

  • In tests - Salesforce does not allow real callouts inside @isTest methods. Use Test.setMock() with a standard HttpCalloutMock implementation. Nimbus enforces the same rule as a real org.
  • In nimbus exec - Apex runs outside of a test context, so Test.setMock() is not available. Use --mocks to provide a config file that Nimbus uses to intercept and respond to any Http.send() calls.

Test Callouts

Salesforce does not allow real HTTP callouts inside @isTest methods - any call to Http.send()without a registered mock throws System.CalloutException. Nimbus enforces the same rule.

The solution is the standard Salesforce pattern: implement HttpCalloutMock and register it with Test.setMock(). Nimbus supports this natively - no special setup required.

Basic example

apex
@isTest
private class UserServiceTest {

    private class UsersMock implements HttpCalloutMock {
        public HttpResponse respond(HttpRequest req) {
            HttpResponse res = new HttpResponse();
            res.setStatusCode(200);
            res.setHeader('Content-Type', 'application/json');
            res.setBody('[{"id": 1, "name": "Alice"}]');
            return res;
        }
    }

    @isTest
    static void testFetchUsers() {
        Test.setMock(HttpCalloutMock.class, new UsersMock());

        Test.startTest();
        List<User__c> users = UserService.fetchUsers();
        Test.stopTest();

        Assert.areEqual(1, users.size());
        Assert.areEqual('Alice', users[0].Name);
    }
}

This code deploys and runs identically on Salesforce and in Nimbus. No Nimbus-specific classes or imports needed.

Multiple endpoints

A single mock class handles all callouts made during the test. Use the request method and endpoint to route responses:

apex
private class ApiMock implements HttpCalloutMock {
    public HttpResponse respond(HttpRequest req) {
        HttpResponse res = new HttpResponse();
        res.setStatusCode(200);
        res.setHeader('Content-Type', 'application/json');

        if (req.getMethod() == 'GET' && req.getEndpoint().contains('/users')) {
            res.setBody('[{"id": 1}]');
        } else if (req.getMethod() == 'POST' && req.getEndpoint().contains('/orders')) {
            res.setStatusCode(201);
            res.setBody('{"id": "ord-001"}');
        }

        return res;
    }
}

Exec Mocking

When running Apex outside of a test context with nimbus exec, Test.setMock() is not available. Any Http.send() call needs to be intercepted at the Nimbus level.

Pass a mock config file with --mocks. Nimbus intercepts all Http.send() calls and returns the matching configured response - no real network request is made.

Using a mock file

bash
nimbus exec MyClass.run --mocks nimbus.mocks.yaml
yaml
# nimbus.mocks.yaml
- method: GET
  url: https://api.example.com/v1/users
  status: 200
  body: '[{"id": 1, "name": "Alice"}]'
  contentType: application/json

- method: POST
  url: https://api.example.com/v1/orders
  status: 201
  body: '{"id": "ord-001", "status": "created"}'
  headers:
    X-Request-Id: abc-123

Inline mocks

For quick one-offs, define mocks directly on the command line:

bash
# method:url:status:body
nimbus exec MyClass.run --mock "GET:https://api.example.com/v1/users:200:[]"

# multiple --mock flags
nimbus exec MyClass.run \
  --mock "GET:https://api.example.com/v1/token:200:{"token":"abc"}" \
  --mock "POST:https://api.example.com/v1/submit:201:{"ok":true}"

Unmatched callouts

If Apex calls an endpoint with no matching mock entry, Nimbus throws an error and halts execution. Use --allow-unmocked to let unmatched calls fall through to the real network instead.

bash
nimbus exec MyClass.run --mocks nimbus.mocks.yaml --allow-unmocked

Sequential responses

List multiple responses for the same endpoint to return them in order. The last response repeats once the list is exhausted.

yaml
# First call → 429, second call onward → 200
- method: GET
  url: https://api.example.com/v1/items
  responses:
    - status: 429
      body: '{"error": "rate limited"}'
      contentType: application/json
    - status: 200
      body: '{"items": [{"id": 1}]}'
      contentType: application/json

Config Reference

Full schema for entries in nimbus.mocks.yaml:

FlagDefaultDescription
method-HTTP method: GET, POST, PUT, PATCH, DELETE, HEAD, TRACE
url-Full URL or URL pattern to match against
status200HTTP status code returned in the response
body""Response body string
contentTypeapplication/jsonValue for the Content-Type response header
headers{}Map of additional response headers (key: value)
responses-List of sequential responses (overrides body/status/contentType for that entry)

Response object (used inside responses:)

FlagDefaultDescription
status200HTTP status code for this response
body""Response body string
contentTypeapplication/jsonContent-Type for this response
headers{}Additional response headers

Committing mock files

Commit nimbus.mocks.yaml alongside your code so the whole team uses the same mock definitions. Use multiple files to separate concerns - one per integration, one per feature area:

bash
nimbus exec MyClass.run --mocks mocks/auth.yaml --mocks mocks/orders.yaml

URL Patterns Pro

Match endpoints by glob pattern instead of exact URL. Useful when your code constructs URLs with dynamic IDs or path parameters.

Wildcard syntax

FlagDefaultDescription
*-Matches any single path segment - no slashes
**-Matches any path, including multiple segments and slashes
yaml
# * matches one segment: /api/v1/users/123/profile, /api/v1/users/abc/profile
- method: GET
  url: https://api.example.com/v1/users/*/profile
  status: 200
  body: '{"name": "Alice"}'

# ** matches any path: /api/v1/orders/123/items/456
- method: DELETE
  url: https://api.example.com/v1/orders/**
  status: 204

Priority

Exact URLs always win over patterns. Among patterns, longer (more specific) ones win. If two patterns are equally specific, the first entry in the file wins.

yaml
# Priority: low → high (top to bottom in this list)

- method: GET
  url: https://api.example.com/v1/users/**    # broadest - lowest priority
  body: '{"fallback": true}'

- method: GET
  url: https://api.example.com/v1/users/*/settings    # wins for .../users/123/settings
  body: '{"theme": "dark"}'

- method: GET
  url: https://api.example.com/v1/users/me    # exact URL - highest priority
  body: '{"id": "me", "admin": true}'

Commands

nimbus test

Run Apex test classes locally. This is the core command you'll use every day.

Pattern matching

bash
nimbus test                              # Run all tests
nimbus test MyTestClass                  # All methods in a class
nimbus test MyTestClass.testMyMethod     # Single test method
nimbus test Calculator*                  # Classes starting with "Calculator"
nimbus test *Test                        # Classes ending with "Test"
nimbus test *Calculator*                 # Classes containing "Calculator"

Path patterns

bash
nimbus test force-app/main/default/classes/MyTest.cls   # Specific file
nimbus test force-app/main/default/classes/              # All in directory

Running nimbus test with no pattern from inside a package subdirectory (e.g. after cd’ing intoforce-app/main/default/classes/billing) auto-scopes to that subdirectory instead of the whole project.

Parallel execution Pro

bash
nimbus test -p 8     # 8 parallel workers (Pro)
nimbus test -p 16    # Push it harder on fast machines
nimbus test           # Sequential (Free tier default)

Coverage

bash
nimbus test --coverage                              # Print summary to console
nimbus test --coverage --coverage-output json       # JSON to console (for scripting)
nimbus test --coverage --coverage-report out.json   # Save JSON to file
nimbus test --coverage --coverage-report out.html   # Save HTML report
nimbus test --coverage --coverage-report out.xml    # Save Cobertura XML (Team)

Org fallback Pro

bash
nimbus test -f -o my-org    # Fall back to SF CLI when Nimbus can't handle a test
nimbus test --fetch-missing -o my-org  # Fetch missing metadata and re-run

When Nimbus surfaces missing metadata (Apex classes, custom fields, custom labels, custom objects, custom metadata types), it also writes a standard SFDX manifest at manifest/nimbus-missing.xml — regardless of whether --fetch-missing is set. You can hand it to sf directly: sf project retrieve start --manifest manifest/nimbus-missing.xml -o <alias>. The filename is distinct from package.xml so it won't clobber a user-managed deployment manifest.

Tracing / debugging

bash
nimbus test --trace                          # Enable execution tracing
nimbus test --trace --trace-level debug      # Verbose trace
nimbus test --trace --trace-output ./traces  # Save traces to custom dir

Org defaults (custom settings)

Seed custom setting org-default records before tests run. Can be specified via CLI flag or committed to nimbus.properties for repeatability.

bash
# CLI flag (one field per flag, or comma-separated fields)
nimbus test --org-default "TriggerSettings__c.IsEnabled__c=true"
nimbus test --org-default "MySettings__c.Field1__c=foo,Field2__c=bar"

# nimbus.properties (committable, profile-aware)
nimbus.seed.org-default.TriggerSettings__c=IsEnabled__c=true
nimbus.seed.org-default.MySettings__c=Field1__c=foo,Field2__c=bar

Exclusions

bash
nimbus test -e __tests__       # Exclude paths containing "__tests__"
nimbus test -e legacy -e wip   # Multiple exclusions
FlagDefaultDescription
-p, --parallel1Number of parallel test workers (Pro)
--shard-Run a subset of tests: --shard N/M. Use with CI matrix to parallelize across jobs.
--local-shards-Run N shards concurrently on this machine, each with its own embedded Postgres: a number (e.g. 4) or "auto" (Pro)
--impactedfalseRun only tests affected by source changes since the impact map was last built. Selects tests whose coverage touched a changed class, plus changed and never-before-seen test classes. Falls back to a full run when no map exists or a schema file changed (sfdx-project.json, objects/). The map is rebuilt after each run.
-n, --dry-runfalseList matching tests without executing them
--timeout60Per-test timeout in seconds (catches infinite loops)
--sandboxfalseMake Organization.IsSandbox return true during tests
-P, --assign-perms-Assign PermissionSet(s) to the mock running user by API name (repeatable, comma-separated)
--show-permission-seamsfalseExpand the permission-seam summary into a per-test list. Custom-permission seams ($Permission, FeatureManagement.checkPermission) are flagged distinctly from FLS / object-perm seams.
--strict-permissionsfalseFail tests whose passing path consulted a permission check outside System.runAs() (CI gate)
--feature-Enable org features: MultiCurrency, PersonAccounts (repeatable, comma-separated)
--faketime-Pin Date.today() / DateTime.now() to this instant (e.g. 2030-01-15T12:00:00Z)
--coveragefalseEnable code coverage collection
--coverage-outputconsoleCoverage output format: console, json
--coverage-report-Path to save coverage report - format auto-detected from extension: .json, .html, .xml Cobertura (Team). Implies --coverage.
--results-xml-Path to save JUnit XML test results (Team)
-f, --fallbackfalseFall back to SF CLI for unsupported features (Pro)
--fetch-missingfalseRetrieve missing metadata and re-run
--org-default-Seed a custom setting org default before tests: ObjectName__c.Field=value (repeatable)
-e, --exclude-Exclude paths containing pattern (repeatable)
-i, --interactivefalseInteractively select package scope
-q, --quietfalseMinimal output — show only the summary and failures
--jsonfalseOutput results as JSON (for CI/agent consumption)
--tracefalseEnable execution tracing
--trace-levelnormalTrace verbosity: minimal, normal, verbose, debug, system
--trace-output.nimbus/tracesDirectory for trace output files

Parallel sharding

Split your test suite across multiple CI jobs with --shard N/M. Job N runs only its assigned slice of test classes. All N jobs run concurrently in a CI matrix, cutting wall-clock time proportionally.

Classes are distributed by recorded test duration (not just count), so shards finish in roughly equal wall time even when a few classes dominate the runtime. Duration data is recorded automatically by full test runs and persisted in your project; a project with no history yet falls back to an even split by class name. All methods within a class are always kept together so @testSetup runs correctly.

When combined with --results-xml, each shard's JUnit XML output includes a shard="N/M" attribute on the root <testsuites> element, allowing CI aggregators to merge results across shards.

yaml
# GitHub Actions matrix example
strategy:
  matrix:
    shard: [1, 2, 3, 4]
steps:
  - run: nimbus test --shard ${{ matrix.shard }}/4 --format junit

Tuning parallel execution

The default of 8 workers is a safe starting point for most machines and projects. You can push higher, but there's a ceiling — and it's not CPU or memory.

Why conflicts happen

Each parallel test worker runs inside its own database transaction. When two workers insert, update, or delete rows in the same table at the same time, PostgreSQL acquires row-level locks. If worker A holds a lock that worker B needs (and vice versa), you get a deadlock or a lock timeout.

Nimbus handles this automatically: transient database errors (deadlocks, lock timeouts, constraint violations) are retried up to 3 times with exponential backoff. If retries are exhausted, the test is re-run sequentially after the parallel phase completes. Your test still passes — it just takes longer. To disable this sequential re-run fallback, set nimbus.test.rerun-failed=false in nimbus.properties.

The problem is probability. With 4 workers, lock conflicts are rare. With 8, they're occasional and retries handle them. With 16+, conflicts become frequent enough that retries are exhausted, sequential re-runs pile up, and total wall-clock time can actually increase.

Finding your sweet spot

The optimal worker count depends on your codebase, not your hardware. Projects where tests touch many different objects can run more workers. Projects where most tests insert into the same few objects (Account, Contact, Lead) hit contention earlier.

bash
# Start with 8 workers
nimbus test -p 8

# If you see 0 sequential re-runs, try pushing higher
nimbus test -p 12

# If you see many sequential re-runs, back off
nimbus test -p 4

# CI: use nimbus.properties so the settings are committed
%ci.nimbus.test.parallel=8
# Disable sequential re-run fallback if you want hard failures instead
%ci.nimbus.test.rerun-failed=false

Watch the test summary for "re-run sequentially" messages. If you see more than a handful, your worker count is too high for the contention profile of your project. Back off until re-runs drop to near zero.

Scaling beyond the single-machine limit

If you've maxed out parallel workers on one machine and still want faster runs, use --shard to distribute across multiple CI jobs. Sharding splits tests across entirely separate processes — each with their own database — so there's zero lock contention between shards. This scales linearly.

Local shards Pro

--local-shards N runs the same sharding model on one machine: N concurrent nimbus test processes, each with its own embedded Postgres instance, splitting the suite the same way --shard does. Pass a number, or auto to let Nimbus decide: it picks a shard count based on available cores, but won't shard at all if the suite's recorded run time (from the last full run, tracked in .nimbus/test-durations.json) is too short to be worth the per-shard process overhead — or if there's no recorded run yet. Tune the cutoff with nimbus.test.local-shards-auto-threshold-ms (default 8000).

bash
nimbus test --local-shards 4       # 4 concurrent shards, 4 separate databases
nimbus test --local-shards auto    # Let Nimbus choose (by cores and suite length)

Each shard gets a persistent background daemon (visible in nimbus daemon list) that stays warm across runs, so repeat invocations skip database startup. Results, JUnit XML, and JSON output are merged into a single report as if the suite had run in one process. --local-shards is mutually exclusive with --shard, requires the embedded database provider, and does not currently support --coverage, --dry-run, --interactive, --write-stubs, --fetch-missing, or --impacted.

Each shard is a separate OS process, so it pays its own startup cost — Go runtime init, daemon handshake, config parsing — once per invocation. On short suites that fixed cost can outweigh the benefit of a second Postgres instance; on longer-running suites it's a smaller fraction of total wall time, so the separate-database win has more room to show through. If you're not seeing a win, try a lower shard count before a higher one — splitting workers across more processes also means fewer workers per shard, which can offset the extra database parallelism.

Per-worker isolation (opt-in)

Set nimbus.test.isolation=per-worker-schema to give each worker its own cloned Postgres schema. Row-level locks and writes stay inside the worker, so -p can scale to your CPU count without cross-worker contention. Default is shared to keep startup cost low — see Parallel Isolation for details.

nimbus test:watch Pro

Continuous testing mode. Watches your Apex files and automatically re-runs tests whenever you save.

bash
nimbus test:watch              # Watch all tests
nimbus test:watch -i           # Select package scope interactively

While watching:

bash
r    # Manually trigger a re-run
c    # Clear the output
q    # Quit (or Ctrl+C)

Watch mode uses test impact selection automatically once an impact map exists: the first run runs everything and builds the map, and each later run narrows to the tests affected by the files you changed (e.g. 3 of 412 tests impacted by AccountService.cls), falling back to a full run when a schema file changes. Pass --no-impact to run the full pattern on every change.

FlagDefaultDescription
-i, --interactivefalseSelect package scope interactively before watching
--no-impactfalseDisable automatic test impact selection; run the full pattern on every change

nimbus validate

Check Apex files for syntax errors and apiVersion mismatches without running tests. Fast — no database or runtime needed. Errors come from the parser; warnings come from the apiVersion check, which reads each file’s *-meta.xml for the declared apiVersion and flags any referenced symbol whose introduction version is newer than that. Catches mistakes like a v52 class callingDatabase.getCursor (v66) before the deploy does.

Beyond warnings, validate flags a handful of deploy blockers — things Salesforce rejects at compile/deploy — and exits non-zero when any are found: REST resource methods with an unsupported return type; setting an AutoNumber field in an SObject constructor (new Log__c(Name = 'x') — AutoNumber fields aren’t writeable); and a concrete class that declaresimplements but doesn’t define every method of the interface. The field and interface checks resolve schema and interfaces from the local project, and skip conservatively when a type can’t be resolved (e.g. a managed-package or cross-file interface) so they never block a deploy they can’t be sure of.

validate also runs semantic analysis across your whole project’s type universe — resolving every type reference, variable, method call, and field against your classes, the Salesforce standard library, and your SObject schema. It flags things the parser accepts but the platform rejects at deploy: unknown types, undefined variables (including through the inheritance chain), unknown, wrong-arity, or wrong-argument-type methods and constructors (s.frobnicate(),add(1, 'two') against add(Integer, Integer),new Widget(1, 2) with no matching constructor — on the standard library and on your own classes), calling another class's private method, instance members from static context (and statics through an instance), duplicate fields, methods, and variables (including block-scope and parameter shadowing), constructing an abstract class, extending a non-virtual class, overriding a non-virtual method or omitting the override keyword, unimplemented abstract methods, reassigning a final field, missing returns and unreachable statements, break outside a loop, illegal assignments, returns, and ternaries (Integer x = 'hello';,List<Integer> xs = new List<String>();, assigning avoid call), non-Boolean conditions, catching or throwing non-exceptions, DML on non-records, iterating non-collections (and loop variables that can't hold the element type), impossible casts and always-false instanceof, incompatible comparisons, ! and ++ on non-numeric types, interface conformance (implementing every interface method — and notimplements-ing a class, or the same interface twice),switch rules (non-switchable expression types, duplicate and type-mismatched when values), SOQL bind variables and unknown columns, source-absent unnamespaced custom SObjects (checked-in SFDX and stubs metadata is authoritative), typed SObject field assignments, integer-only SOQL LIMIT/OFFSET binds, and unknown SOSL return fields,Datetime-to-Date narrowing, this()/super()constructor chaining, private field access across classes, protected members and@isTest methods in the wrong kind of class, abstract methods with bodies, access modifiers on interface methods, @future and@InvocableMethod shape rules, test-setup and web-exposure annotation contracts (@AuraEnabled, @RemoteAction, REST, andwebservice), illegal class, constructor, field, and method modifiers, annotation placement and context rules for @testSetup,@ReadOnly, and HTTP methods, REST URL mappings and per-verb method cardinality, property, nested-class, interface-method, and enum modifiers, circular self-inheritance, constructor-chain placement, generic type arity, inner-class nesting and static rules, abstract/static modifier combinations, catch ordering, property accessor visibility, global members in non-global classes, duplicate and unknown enum values, duplicate declaration scopes, protected/static field combinations, nested override visibility and return types, class-qualified instance calls, generic array shapes,@InvocableVariable field contracts, annotation property names,@SuppressWarnings arguments, duplicate and illegally modified method parameters, interface return and visibility contracts, classes extending interfaces, instance-initializer returns, construction of the built-in Exception, conflicting sharing modes, @AuraEnabled enums, private@RemoteAction methods, @TestVisible parameters, and unknown fields on your classes and SObjects. Method and conversion rules run against a catalog generated from the platform compiler's own symbol table and are verified against real check-only deploys — quirks likeDatabase.insert(records, false), ''.isBlank(), variables that shadow type names, methods named like their class, conversion-covariant collections, and keyword-less abstract-method implementations all resolve exactly as the org compiler would.

The current differential corpus catches 233 of 235 org-rejected error classes, each verified against a real check-only deploy, with zero intrinsic false rejects in the deployable-source gate. The two deferred cases both require managed-package context to decide whether an identifier may use @Deprecated.

Resolution is conservative by design: anything it can’t disprove — managed-package namespaces, inherited members, and unsynced field types — resolves rather than flags, so it never reports a deployable file. Every rule is gated on a corpus of thousands of real, deployable open-source classes before release. Findings are deploy blockers by default — like the platform, validate fails on code a check-only deploy would reject. Use --sema=warn to downgrade findings to warnings (e.g. while a stale schema sync is being refreshed), or --sema=off to disable.

bash
nimbus validate                  # Validate all Apex files
nimbus validate MyClass          # Files matching "MyClass"
nimbus validate *Service*        # Files matching "*Service*"
nimbus validate -e __tests__     # Exclude paths containing "__tests__"
nimbus validate --sema=warn      # Downgrade semantic findings to warnings
nimbus validate --json           # Machine-readable JSON output (for editor integration)

Path patterns

bash
nimbus validate force-app/main/default/classes/MyClass.cls   # Specific file
nimbus validate force-app/main/default/classes/               # All files in directory
nimbus validate src/tests/                                    # All files under path
FlagDefaultDescription
-e, --exclude-Exclude paths containing pattern (repeatable)
-i, --interactivefalseSelect package scope interactively
--semaerrorSemantic analysis level: error (default, deploy-blocker), warn, or off.
--jsonfalseOutput results as JSON, including apiVersion and semantic warnings under a per-file warnings[] array.

Suppress an individual apiVersion warning with // nimbus:ignore on the same line as the call (or the line directly above). Add a comma-separated rule list to scope to specific rules.

Run from inside a package subdirectory (e.g. after cd’ing intoforce-app/main/default/classes/billing) to auto-scope to that subdirectory instead of the whole project — no pattern argument needed. Same behavior applies to nimbus test.

For CI gates, the standalone apex-version-lint CLI runs the same check against an SFDX project tree and exits non-zero with -strict:

bash
apex-version-lint              # warn-only, exit 0
apex-version-lint -strict      # exit 1 if any diagnostic was emitted
apex-version-lint path/to/src  # walk a specific subtree

nimbus exec

Execute anonymous Apex - like Salesforce's "Execute Anonymous", but locally.

bash
# From a file
nimbus exec script.apex

# Inline expression (auto-wrapped in System.debug)
nimbus exec -c "Calculator.add(1, 2)"
nimbus exec -c "new Account(Name = 'Test').Name"

# Multi-statement inline
nimbus exec -c "Integer x = 5; Integer y = 10; System.debug(x + y);"
FlagDefaultDescription
-c, --code-Inline Apex code to execute

nimbus fixture

Feature overview: test data.

Generate a TestDataFactory with a create<SObject>() method per object, with required fields pre-filled from the local schema. Standard objects work with zero sync (Nimbus ships their describes); custom objects need a sync. Prints to stdout by default.

bash
nimbus fixture Account Contact          # print a TestDataFactory to stdout
nimbus fixture My_Object__c --write     # save it to the default package

Required lookups are generated, not left as TODOs

When an object has a required lookup, Nimbus follows it: the parent object is added to the factory automatically (even if you did not name it), methods are emitted parents-first so they can actually be inserted in order, and a create<SObject>WithParents() method inserts the parents and wires the child to them.

bash
$ nimbus fixture LoyaltyTransaction__c

    static Account createAccount() { ... }

    static LoyaltyTransaction__c createLoyaltyTransaction__c() {
        return new LoyaltyTransaction__c(
            Points__c = 1,
            Reason__c = 'Purchase'
        );
    }

    // Inserts the required parents, then returns an un-inserted record wired to them.
    static LoyaltyTransaction__c createLoyaltyTransaction__cWithParents() {
        Account parent0 = createAccount();
        insert parent0;
        LoyaltyTransaction__c record = createLoyaltyTransaction__c();
        record.Account__c = parent0.Id;
        return record;
    }

A required lookup to the object's own type has no root to generate from, so it becomes an explicit TODO rather than infinite recursion. Lookup cycles between two objects are reported as a warning instead of being silently broken. A required parent with no local schema also becomes a named TODO (plus a warning to run nimbus sync) — never a call to a create method that was not generated, which would produce a factory that does not compile.

Values the schema vouches for

Picklist fields use a real value from the field's value set, not a placeholder — a restricted picklist rejects anything else, so a fabricated value produces a factory that always fails on insert. When the local schema has no value set, the field is marked unresolved rather than guessed. The generated class is deterministic: the same project always produces the same file, so it diffs cleanly.

FlagDefaultDescription
--classTestDataFactoryGenerated class name.
--writefalseWrite the class to the default package instead of stdout.
--api-version62.0API version for the -meta.xml when --write.

nimbus coverage diff

Compare two JSON coverage reports (from nimbus test --coverage-report cov.json) and report the overall coverage delta, per-file regressions and improvements, and the exact lines that went from covered to uncovered. Built for PR coverage gates.

bash
# baseline on main, then the branch:
nimbus test "*" --coverage-report base.json
nimbus test "*" --coverage-report pr.json
nimbus coverage diff base.json pr.json --fail-on-drop
FlagDefaultDescription
--fail-on-dropfalseExit non-zero when overall coverage decreased (CI gate).

nimbus mutate Pro

Run mutation testing against your Apex code. Nimbus introduces small changes (mutations) to your production code - flipping operators, negating conditions, changing return values - then runs your tests against each mutant. If your tests catch the change, the mutant is "killed." If all tests still pass, the mutant "survived," revealing a gap in your test suite.

This is a technique widely used in Java (PITest), JavaScript (Stryker), C# (Stryker.NET), and Python (mutmut) - but has never been possible for Apex because org-based test execution is too slow. Nimbus runs tests locally in milliseconds, making hundreds of mutations feasible in seconds.

The positional pattern selects which production classes to mutate. If it resolves only to test classes (e.g. AccountServiceTest), Nimbus mutates the class under test instead — derived by stripping conventional Test prefixes/suffixes — and runs that test class against each mutant. This makes nimbus mutate AccountServiceTest answer the natural question: "is this test suite actually any good?"

bash
# Mutate all production classes, run all tests
nimbus mutate

# Mutate a specific class
nimbus mutate AccountService

# Pass a test class — Nimbus mutates the class under test and runs that test
nimbus mutate AccountServiceTest

# Mutate a class, only run specific tests per mutant
nimbus mutate --class AccountService --test AccountServiceTest

# Increase per-mutant timeout to 60 seconds
nimbus mutate --timeout 60

# Only print surviving mutants during the run (kills suppressed)
nimbus mutate --survivors-only

# Fail the run (exit code 1) if mutation score is below 80%
nimbus mutate --min-score 80

# Emit a machine-readable JSON report for CI (exit code still set by --min-score)
nimbus mutate --json --min-score 80

Mutation Types

TypeExampleWhat it tests
Arithmetic+ → -, * → /Your tests verify calculation results, not just that code runs
Comparison< → <=, > → >=Boundary conditions are checked
Negate conditional== → !=, < → >=Conditional branches are actually tested
Logical&& → ||Compound conditions aren't over-simplified
Boolean literaltrue → falseBoolean flags affect behavior
Increment/decrement++ → --Loop and counter logic is validated
Negate prefixRemove !Negation logic is tested
Return valuereturn x → return nullReturn values are used by callers
Integer literalN → N+1Off-by-one and boundary constants are asserted, not just threaded through
String literal"foo" → ""String content is asserted, not just presence (SOQL fragments and sObject type names are skipped)
DML verbinsert → update, delete → undeleteThe right persistence operation is being invoked, not just any DML
Call removalservice.doThing(); → no-opSide-effecting calls are asserted (assertion helpers on System/Assert/Test are skipped to keep the score honest)
Loop boundaryi < ni < n + 1 / i < n - 1Loops iterate the intended number of times — catches off-by-one against variable bounds, which integer ±1 and boundary swaps miss

Flags

FlagDefaultDescription
--class-Only mutate this class (default: all non-test classes)
--test*Test pattern to run against each mutant
--timeout30Per-mutant timeout in seconds
--survivors-onlyfalseOnly print surviving mutants during the run (kills, timeouts, and errors are suppressed). The final summary still lists totals and surviving mutants.
--min-score0Exit with code 1 if the final mutation score is below this threshold (0-100). Useful for CI gating.
--jsonfalseEmit a single machine-readable JSON report to stdout (mutationScore, total, killed, survived, timedOut, errors, survivors[]) and suppress the human summary. Combine with --min-score to get both the report and the exit code. This is the format the release mutation gate reads.

Understanding the Score

The mutation score = killed mutants / total mutants × 100%. A project with 95% line coverage but 40% mutation score has tests that execute code without verifying behavior. Think of it as the difference between "I walked through every room" and "I checked every room for problems."

nimbus explain

Feature overview: failure intelligence.

Explain one test's outcome in full: the exception, the source location, the assertion's expected and actual values, and the SOQL and DML executed just before it failed.nimbus test output stays terse - this is the explicit expansion, so nothing about a normal run changes.

bash
# Explain a specific failure
nimbus explain AccountServiceTest.testInsertAccount

# Structured output for scripts and agents
nimbus explain AccountServiceTest.testInsertAccount --json

# Mask runtime values before pasting into an issue or chat
nimbus explain AccountServiceTest.testInsertAccount --redact

# What does this command keep, and where?
nimbus explain --retention-policy

What it prints

bash
ZZDiagFailTest.assertionFailsWithOperationTail — FAILED (8ms)

  System.AssertException
  Assertion failed: expected 999, got 1. diagnostics fixture: expected 999 accounts

  at force-app/main/default/classes/ZZDiagFailTest.cls:23

  Assertion
    expected  999
    actual    1

  Last operations before the failure (most recent last)
    dml   insert Account  (1 row)  ZZDiagFailTest.cls:18
    soql  SELECT Id, Name FROM Account WHERE Name = 'Diagnostics Fixture Co'  ZZDiagFailTest.cls:20

  Not established by this explanation
    - Explanation is derived from local execution only; the org remains the final authority.
    - Record-level mutation provenance is not recorded.
    - No comparison against a previous passing run is available.

Last passed

When history holds a run in which the test passed, the report says when — and whether the Apex source has changed since. An unchanged fingerprint means the code is not what differs, which separates a regression from non-determinism. It does not close the question: test data, ordering, time, configuration and org metadata all sit outside the fingerprint, and the report says so.

It states what it does not know

Every explanation ends with what it could not establish. Nimbus runs Apex locally at high fidelity, but it is not the Salesforce platform: an absent record in the evidence means "not observed", not "did not happen". The report says so rather than letting you assume otherwise - and it names the boundaries of the feature itself, so an empty operation tail is never mistaken for a test that ran no queries.

Flags

FlagDefaultDescription
--jsonfalseEmit the versioned JSON explanation (schema: nimbus.explain/v1)
--redactfalseMask assertion operands, SOQL string literals, and their occurrences in the exception message
--retention-policyfalsePrint what this command keeps, and where

Retention and redaction

Explanations are computed in-process and are not written to disk, cached, or uploaded. Operands and SOQL text are shown verbatim by default, because the local developer already has that data in front of them. Use --redact for output that leaves the machine; it deliberately over-masks, keeping structure (object, fields, exception type, location) so a redacted report stays diagnosable.

Same contract for agents

The MCP tool explain_failure returns the same versioned payload, built from the same code path, so a human and an agent never read divergent accounts of one failure. It answers from the previous run when possible and otherwise runs just that test.

nimbus triage

Run tests and group the failures by the cause the engine recorded when each one happened. Built for the first run on an unfamiliar codebase, where the output is a wall of failures and the question is "how many distinct problems is this, really?"

bash
# Triage the whole suite
nimbus triage

# Triage one class
nimbus triage "AccountTest.*"

# Structured output (schema: nimbus.triage/v1)
nimbus triage --json

# Show at most 3 tests per group
nimbus triage --limit 3

What it prints

bash
Triage — 133 failed of 2357 tests (*)

  Grouped by engine-assigned cause

    assertion.failed             93 test(s)
      An assertion in the test did not hold

    loaddata.resource-not-found  2 test(s)
      Test.loadData referenced a static resource the project does not contain
      fix: Retrieve the static resource, or check the resource name passed to Test.loadData.

  No cause recorded (40) — listed individually, not grouped
    ACCT_IndividualAccounts_TEST.testOwnerLastNameUpdate — Invalid field Owner for Account

  Run-level evidence (2) — not attributed to any failure
    Apex Classes             ABadClass

It is not a classifier

Every group corresponds to a cause some part of the engine assigned at the moment of failure — a Test.loadData resource that was absent, a @testSetup that threw, a field the schema provider looked up and did not find. Nothing inspects message text to decide where a failure belongs.

Failures the engine could not classify are listed individually, never folded into whichever group looks plausible. A null-pointer exception is a symptom whose cause could be a missing stub, an unpopulated field, or a genuine bug — no pattern match can tell those apart, and a wrong grouping sends you to fix the wrong thing while hiding the failure inside a count.

Run-level evidence

Missing metadata is reported separately and deliberately not attributed to individual failures. Nimbus degrades an absent definition to null or zero rows instead of throwing, so the failure surfaces later, somewhere that no longer knows a schema gap caused it. The gap is real and worth fixing; which test it broke is not something the engine can honestly claim.

Flags

FlagDefaultDescription
--jsonfalseEmit the versioned JSON triage report (schema: nimbus.triage/v1)
--limit5Max tests listed per group (0 = all)

Suite health in doctor

nimbus doctor --suite runs the same triage and appends a ranked "fix in this order" list to doctor's setup checks - largest cause first, so a red suite becomes a short ordered worklist instead of a wall of failures. It is opt-in because it runs the tests; plain nimbus doctor stays fast enough for a pre-flight check. With --json the report appears undersuite_health.

nimbus history

Browse past test runs in an interactive TUI. Every nimbus test run is recorded under .nimbus/history/; this command reads it back — recent runs, their pass/fail breakdowns, and a flaky-test report. Free.

bash
# Browse recent test runs
nimbus history

# Flaky report: same test, same conditions, different outcome
nimbus history --flaky

# Show the last 50 runs
nimbus history --limit 50

Piped output is JSON

When stdout is not a terminal — a pipe, a CI runner — nimbus history skips the TUI and emits the run list as JSON, so nimbus history --limit 50 | jq works without a TTY.

Flags

FlagDefaultDescription
--flakyfalseShow the flaky test report instead of the run list
--limit20Number of recent runs to display

History also powers nimbus explain's "last passed" comparison and the trend views in traces & analytics.

nimbus graph

Feature overview: dependency graph.

Show which classes reference a class — directly and transitively — and which tests relate to it. A navigation aid for "what does changing this touch?"

bash
nimbus graph                     # project overview: size, cycles, most-depended-upon
nimbus graph AccountService      # what reaches one class
nimbus graph AccountService --json

Bare nimbus graph prints the orientation view: how many classes, triggers and edges, the cycle count, and the most-depended-upon classes — the step before you know which class to ask about. With --json it emits the full nimbus.graph/v1 payload instead.

It is not a test-selection decision

This reads syntactic references. We measured that reading against real coverage across five projects and found it unsafe for choosing which tests to skip: a trigger dispatching through custom metadata reaches its handler with no reference to follow, so on one real project it found 1 of the 46 test classes that genuinely exercise a handler. The command says so, and refuses to present itself as a selection tool.

Measured alongside, not merged

When a coverage map exists, the tests that actually executed the class are shown as a separate answer, along with any that no syntactic path reaches — a direct measurement of the gap:

bash
ACCT_IndividualAccounts_TDTM — reachability

  Tests reached syntactically (1)
      ACCT_IndividualAccounts_TEST

  Tests a coverage run actually observed executing it (46)
      ACCT_AdministrativeNameRefresh_TEST
      ...

  Observed but NOT reachable syntactically (45) — the blind spot, measured

  Not established by this view
    - Syntactic references only. A trigger dispatching through custom metadata
      reaches handlers with no reference to follow.
    - This is reachability, not a test-selection decision.

The two are deliberately not combined into one number. Where they disagree, the disagreement is the useful part.

Circular dependencies

nimbus graph --cycles lists every circular dependency, largest first, with a shortest loop through each — so the output says where to cut rather than only that a cycle exists.

bash
$ nimbus graph --cycles

    8 classes: Advancement_Adapter, Advancement_Info, TDTM_Config, ...
      shortest loop: Advancement_Adapter → Advancement_Info → Advancement_Adapter
      cut any one edge on that loop to break it

Classes that reference themselves are counted separately rather than listed: in Apex that is nearly always a class qualifying its own statics or an inner class, and printing dozens of them buries the finding that matters. Reported cycles are a lower bound — edges invisible to a syntactic reading may close further loops.

Export and visualise

--format dot|mermaid|json exports the graph. Edges only a coverage run revealed are drawn dashed, triggers get a distinct shape, and the limits travel inside the file as comments — an exported diagram outlives the session that explained it. Past 300 nodes the export refuses and tells you how to narrow, rather than emitting an unreadable hairball.

bash
nimbus graph --format dot | dot -Tsvg -o graph.svg
nimbus graph AccountService --depth 2 --format mermaid

# Raw Mermaid is not meant to be read in a terminal. Write a .md instead and
# open it — VS Code's preview, GitHub and Obsidian all render the diagram.
nimbus graph AccountService --out graph.md

With --out, a .md path wraps the diagram in a Markdown document: a fenced Mermaid block that renders natively, the cycles listed underneath, and the limits as prose rather than as comments — a comment is invisible once rendered, which is exactly when someone is most likely to read the picture as complete.

An interactive view is available in the Dev UI, VS Code (Nimbus: Show Dependency Graph) and IntelliJ, all rendering the same nimbus.graph/v1 payload. It reads like a note-graph tool: hovering a class highlights its direct neighbourhood and fades the rest, clicking pins the highlight, and search filters by name. Test classes and unconnected nodes can be hidden, the layout forces are adjustable, and nodes can be dragged. Labels appear on hubs first and fade in as you zoom. In VS Code, double-clicking a node opens the file.

Flags

FlagDefaultDescription
--cyclesfalseList circular dependencies, largest first
--formatExport the graph: dot, mermaid or json
--depth2With a class name, how many hops of neighbourhood to include
--outWrite to a file instead of stdout; a .md path wraps the diagram so VS Code, GitHub and Obsidian render it
--jsonfalseEmit the report as JSON (schema: nimbus.graph/v1)

nimbus bench Pro

Run a test method N times and report timing statistics: mean, median, p95, p99, min, max. Useful for catching slow SOQL, comparing performance before and after refactors, and setting performance budgets in CI.

While the benchmark runs, a live progress view updates with rolling stats after each sample. Press q to stop early and show results from completed runs.

bash
# Benchmark a test method (100 runs, warmup enabled by default)
nimbus bench AccountServiceTest.testInsertAccount

# 50 runs, no warmup
nimbus bench AccountServiceTest.testInsertAccount --runs 50 --warmup=false

# JSON output - pipe to jq for CI integration
nimbus bench AccountServiceTest.testQuery --format json

# CSV output - one row per sample
nimbus bench AccountServiceTest.testQuery --format csv > results.csv

CI integration

JSON output pipes cleanly to jq. Fail your pipeline if p95 exceeds a threshold:

bash
nimbus bench AccountServiceTest.testQuery --runs 50 --format json \
  | jq 'if .p95_ms > 100 then error("p95 exceeded 100ms") else . end'

Flags

FlagDefaultDescription
-n, --runs100Number of times to run the test method
--warmuptrueDiscard the first 10% of runs to exclude cold-start overhead
-f, --formattableOutput format: table, json, csv

Bench history

After each run, results are appended to .nimbus/bench-history.jsonl - one JSON line per benchmark with timestamp, method, and all timing stats. Use this file to track performance trends over time in external tooling.