Triggers, bulk DML, SOQL queries, CI pipelines - running on your machine instead of waiting for an org.
Today, testing a trigger means deploying to an org, running the test, reading the debug log in Setup, finding the failure, fixing it, deploying again. With Nimbus, you save the file and see the result in milliseconds.
Trigger.new, Trigger.old, Trigger.newMap, and Trigger.oldMap all work. Before triggers can modify fields in-place. After triggers see the committed state. The full order of execution is respected - before triggers, validation, save, after triggers.
// AccountTrigger.trigger
trigger AccountTrigger on Account (before insert) {
for (Account acc : Trigger.new) {
if (acc.Industry == null) {
acc.Industry = 'Other';
}
}
}
// AccountTriggerTest.cls
@isTest
private class AccountTriggerTest {
@isTest
static void testDefaultIndustry() {
Account acc = new Account(Name = 'Acme');
insert acc;
Account result = [SELECT Industry FROM Account
WHERE Id = :acc.Id];
System.assertEquals('Other', result.Industry);
}
}Testing bulk operations in an org means slow inserts, governor limit exposure, and leftover test data that pollutes the next run. Locally, 200 records insert in milliseconds and every test gets a clean slate.
DML operations execute against an embedded PostgreSQL database with the same semantics as Salesforce. Each test runs in its own transaction that's rolled back afterward - full isolation, zero cleanup.
@isTest
private class BulkOperationsTest {
@isTest
static void testBulkInsert() {
List<Contact> contacts = new List<Contact>();
for (Integer i = 0; i < 200; i++) {
contacts.add(new Contact(
FirstName = 'Test',
LastName = 'Contact ' + i,
Email = 'test' + i + '@example.com'
));
}
Test.startTest();
insert contacts;
Test.stopTest();
List<Contact> results =
[SELECT Id FROM Contact];
System.assertEquals(200, results.size());
}
}Complex queries with bind variables and relationship subqueries won't fail until after a deploy - if you're testing in an org. Nimbus translates your SOQL to real SQL and runs it against actual data, before you deploy.
SOQL queries are translated to PostgreSQL on the fly. Bind variables, relationship queries, aggregate functions, LIKE expressions, and ORDER BY/LIMIT all work. @testSetup methods run once and the state is cloned per test.
@isTest
private class QueryTest {
@testSetup
static void setup() {
Account acc = new Account(
Name = 'Acme Corp',
Industry = 'Technology'
);
insert acc;
insert new Contact(
AccountId = acc.Id,
FirstName = 'Jane',
LastName = 'Doe'
);
}
@isTest
static void testRelationshipQuery() {
String industry = 'Technology';
List<Account> accs = [
SELECT Name,
(SELECT FirstName, LastName
FROM Contacts)
FROM Account
WHERE Industry = :industry
];
System.assertEquals(1, accs.size());
System.assertEquals(1, accs[0].Contacts.size());
}
}Flows are the fastest-growing automation tool in Salesforce - and the hardest to test. There's no way to unit test a Flow on the platform. Nimbus executes record-triggered flows, autolaunched flows, and platform event flows locally, so you can verify your automation end-to-end from Apex tests.
Nimbus reads your .flow-meta.xml files, evaluates entry conditions, and executes flow elements - decisions, loops, record creates/updates/deletes, formulas, subflows, collection processors - all locally. Record-triggered flows fire automatically on DML, just like in a real org. No deploy, no scratch org, no waiting.
@isTest
private class OpportunityFlowTest {
@isTest
static void testStageChangeFlow() {
// Insert an opportunity - record-triggered
// flows fire automatically on DML
Opportunity opp = new Opportunity(
Name = 'Big Deal',
StageName = 'Prospecting',
CloseDate = Date.today().addDays(30),
Amount = 50000
);
insert opp;
// Update stage - triggers the "Opp Stage Change"
// record-triggered flow (before save)
opp.StageName = 'Closed Won';
update opp;
// Verify the flow's assignments executed
Opportunity result = [
SELECT StageName, Description
FROM Opportunity WHERE Id = :opp.Id
];
System.assert(
result.Description.contains('Closed Won'),
'Flow should have updated description'
);
}
}Setting up Apex tests in CI usually means a connected app, a JWT cert, a DevHub with scratch org limits, and a pipeline that breaks when any of those expire. Nimbus needs none of that. No sandboxes. No scratch orgs. Just install, test, and get standard output formats that plug into the tools you already use.
Nimbus produces JUnit XML for test results and Cobertura XML for coverage - the same formats used by Java, Python, and JavaScript ecosystems. They plug directly into SonarQube, Codecov, GitHub quality gates, and CI dashboards. No connected org, no scratch org pool, no credentials. Your Salesforce pipeline finally works like every other team's.
# .github/workflows/apex-tests.yml
name: Apex Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Nimbus
run: |
curl -fsSL https://install.testnimbus.dev | sh
- name: Run Apex tests
run: |
nimbus test \
--coverage --coverage-report coverage.xml \
--results-xml results.xml
- name: Upload to Codecov
uses: codecov/codecov-action@v4
with:
files: coverage.xml
- name: Publish test results
uses: dorny/test-reporter@v1
with:
name: Apex Tests
path: results.xml
reporter: java-junitNimbus meets you where you already work - VS Code, IntelliJ, the terminal, or a browser. Every interface talks to the same local runtime, shares the same database, and shows the same results. No context switching, no re-running tests to get data somewhere else.
The VS Code extension communicates with the Nimbus daemon over a local socket - the same runtime used by the CLI. The live debugger implements the Debug Adapter Protocol so VS Code's native debugger UI works out of the box: set breakpoints in your test or production class, step in/over/out, inspect variables with full type information. Switch between editor, terminal, and Dev UI freely; they all share state.
# VS Code Extension - install from Marketplace: "Nimbus Apex"
#
# Run tests:
# CodeLens "Run Test" / "Debug Test" above every @isTest method
# Test Explorer panel: full test tree, pass/fail, timing, inline errors
#
# Code intelligence:
# Coverage gutter icons - green / red / yellow per line
# Governor limits view - color-coded usage bars per test
# SOQL preview - hover any query to see translated SQL
# Trace viewer - call tree + timeline waterfall + filterable log
#
# Productivity:
# Schema explorer - browse objects, fields, relationships
# Execute Anonymous - run any Apex without a test class
# Watch mode - tests re-run on save, results appear inline
# Test history - trends, flaky detection, run comparison
# IntelliJ IDEA Plugin: same feature set - coming soon.
# Both editors talk to the same Nimbus daemon.
# Dev UI (browser dashboard):
nimbus dev
# → http://localhost:7890
# → Real-time results with live WebSocket updates
# → Coverage summary with per-file breakdown
# → Schema explorer with row counts
# → Execute Anonymous with structured outputNew developer joins the project. The old way: request org access, wait for admin, configure credentials, sync metadata. The Nimbus way: clone the repo and run tests.
Nimbus reads only from your repo - metadata that exists only in the org won't be visible. --fetch-missing is a targeted shortcut: it fetches only what the running tests actually reference, then re-runs. It won't replace a full retrieve, but it keeps you moving without switching context.
# Prerequisite (one-time, per team):
# All metadata must be in the repo.
# Retrieve everything from the org:
sf project retrieve start --target-org YourOrg
git add . && git commit -m "chore: retrieve all org metadata"
# During development, if a test references metadata
# that isn't in the repo yet, Nimbus fetches just
# what the tests need and re-runs automatically:
nimbus test --fetch-missing -o YourOrg
# ---
# Day 1 for every new developer after that:
# 1. Clone the repo
git clone git@github.com:your-org/salesforce-app.git
cd salesforce-app
# 2. Install Nimbus (one-time)
brew install nimbusdev/tap/nimbus
# 3. Initialize the local environment
nimbus init
# 4. Run the full test suite - no org access required
nimbus test
# 5. Start developing with instant feedback
nimbus test:watchThe Apex Replay Debugger works by replaying execution from a debug log - after the test already ran, on the org. Nimbus debugs live: set a breakpoint, execution pauses right there, and you inspect variables, stack trace, and object state in real time. No log to download, no replay to set up, no org required.
Because Nimbus runs a full Apex interpreter locally, it can pause execution at any line - in real time, not after the fact. The VS Code extension communicates with the runtime to set breakpoints, receive pause events with stack traces, and let you step through code. Unlike the Replay Debugger, you're debugging live execution: change a variable's value in your head, step forward, see what happens next. No debug log download, no checkpoint setup, no org round-trip.
# Set breakpoints in VS Code and run:
nimbus test "AccountServiceTest.testBulkUpdate"
# When the breakpoint hits:
# → See the full call stack
# → Inspect local variables with types
# → Expand SObjects to see all fields
# → Expand collections to see every element
# → Step In / Step Over / Step Out
# → Continue to next breakpoint
#
# Step modes:
# Step In → Enter the next method call
# Step Over → Execute next line, skip internals
# Step Out → Run to return, pause in caller
#
# No deploy. No debug log levels. No Setup UI.
# Just breakpoints, like every other language.Apex debug logs are a wall of unstructured text with no hierarchy, no timing, and no way to filter. Nimbus traces are structured, hierarchical, and show you exactly what happened - every method call, every SOQL query, every DML operation, every trigger, with precise timing.
Traces use OpenTelemetry-style spans with parent-child relationships, so you see the full execution tree - not a flat log. A method calling a SOQL query that fires a trigger shows as nested spans with individual timing. View traces in VS Code or the Nimbus Dev UI. Five verbosity levels let you zoom from high-level test flow down to every single evaluation step.
# Run tests with tracing enabled
nimbus test "OrderProcessingTest.*" --trace
# Trace verbosity levels:
# minimal → Test/method boundaries only
# normal → DML, SOQL, exceptions, branches
# verbose → Variable assignments, loop iterations
# debug → Every evaluation step
nimbus test --trace --trace-level verbose
# Output: structured JSONL in .nimbus/traces/
# Each span includes:
# - Span hierarchy (parent → child)
# - Precise timing (microsecond resolution)
# - SOQL queries with row counts
# - DML operations with record counts
# - Trigger execution with event type
# - System.debug() output with source locationIn most Salesforce projects, developers run tests manually - after a deploy, when they remember to. With watch mode, tests run on every save. You find bugs the second you introduce them, not 30 minutes later.
Watch mode uses the Nimbus daemon to keep the database warm and AST cache hot. Only changed classes are re-parsed, and only affected tests re-run. The result: sub-second feedback for most edits.
# Start watch mode
nimbus test:watch
# Nimbus watches your force-app/ directory
# for any .cls or .trigger file changes.
#
# When you save a file:
# 1. Changed classes are re-parsed (~10ms)
# 2. Affected tests are identified
# 3. Tests re-run automatically (<500ms)
# 4. Results appear inline
#
# Interactive controls:
# [a] Run all tests
# [f] Run only failed tests
# [p] Filter by pattern
# [q] QuitLine coverage tells you what code ran. Mutation testing tells you if your tests actually verified anything. Nimbus mutates your production code - flips operators, negates conditions, swaps returns - and runs your tests against each change. Surviving mutants expose tests that execute code without checking results.
nimbus mutate runs your full test suite against each mutation. In Java, PITest does this. In JavaScript, Stryker. In Python, mutmut. Nimbus brings mutation testing to Apex for the first time - because local execution makes hundreds of mutations feasible in seconds instead of hours.
// Your production code:
public Integer calculateDiscount(Integer quantity) {
if (quantity > 10) { // Mutant: change > to >=
return quantity * 5; // Mutant: change * to /
}
return 0; // Mutant: change 0 to 1
}
// Your test:
@isTest static void testDiscount() {
Integer result = new PricingService().calculateDiscount(20);
System.assertEquals(100, result); // Catches the * → / mutant ✓
// But do you test the boundary at exactly 10?
// Do you test that quantity <= 10 returns 0?
}AI agents like Claude Code and Cursor work in tight write-test-fix loops. They generate Apex, run tests, read failures, and iterate. But that loop breaks when 'run tests' means a 10-minute org deploy. Nimbus keeps the agent loop fast.
Because nimbus test is a simple CLI command with JSON output, any AI coding agent can invoke it, parse the results, and iterate - just like it would with pytest or jest. No org credentials, no deploy step, no browser-based debug logs. The agent stays in the terminal and moves fast.
# AI agent workflow with Nimbus:
# 1. Agent generates or modifies an Apex class
# → AccountTriggerHandler.cls updated
# 2. Agent runs tests locally
nimbus test "AccountTriggerTest.*" --json
# 3. Agent reads JSON results, spots the failure:
# "Expected 'Technology', got null"
# 4. Agent fixes the handler, runs tests again
nimbus test "AccountTriggerTest.*" --json
# 5. All green - agent moves to next task
# Total elapsed: ~15 seconds
# With org testing: ~15 minutesPoint Nimbus at any SFDX project and run your tests locally in seconds.