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 renmy runtime, 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-save flows, before triggers, validation and duplicate rules, save, after triggers, then assignment rules, workflow rules, processes and after-save flows. An assignment rule or workflow field update saves the record once more and fires the update triggers again.
// 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, so every test is isolated and needs no 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. renmy runtime 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. renmy runtime executes record-triggered flows, autolaunched flows, and platform event flows locally, so you can verify your automation end-to-end from Apex tests.
renmy runtime 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, without a deploy or a scratch org.
@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. renmy runtime needs none of that, and no sandbox or scratch org either. Install it, run the tests, and get standard output formats that plug into the tools you already use.
renmy runtime 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. There is no connected org, scratch org pool, or Salesforce credential in the pipeline, only the renmy license key as a secret, so your Salesforce pipeline 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 renmy runtime
run: |
curl -fsSL https://install.renmy.net | sh
- name: Run Apex tests
run: |
renmy test \
--coverage --coverage-report coverage.xml \
--results-xml results.xml
env:
NIMBUS_LICENSE_KEY: ${{ secrets.NIMBUS_LICENSE_KEY }}
- 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-junitrenmy runtime runs 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. You do not switch context or re-run tests to see the data somewhere else.
The VS Code extension communicates with the renmy 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 the Marketplace: "Renmy - Local Apex Runtime"
#
# 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 / orange 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 in the status bar
# Test history - trends, flaky detection, run comparison
# JetBrains plugin: run, debug, coverage, traces, history, schema, watch mode.
# Both editors talk to the same renmy daemon.
# Dev UI (browser dashboard):
renmy dev
# → opens http://localhost:<port> in your browser
# → Real-time results with live WebSocket updates
# → Coverage summary with per-file breakdown
# → Schema explorer with row counts
# → Execute Anonymous with structured outputA new developer joins the project. Without renmy runtime, they request org access, wait for an admin, configure credentials, and sync metadata. With renmy runtime, they clone the repo and run the tests.
renmy runtime 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, renmy fetches just
# what the tests need and re-runs automatically:
renmy 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 renmy runtime (one-time)
brew install renmy-solutions/nimbus/renmy
# 3. Initialize the local environment
renmy init
# 4. Run the full test suite - no org access required
renmy test
# 5. Start developing with instant feedback
renmy test:watchThe Apex Replay Debugger works by replaying execution from a debug log, after the test already ran on the org. renmy runtime debugs live: set a breakpoint, execution pauses right there, and you inspect variables, stack trace, and object state in real time. There is no log to download, no replay to set up, and no org required.
Because renmy runtime runs a full Apex interpreter locally, it can pause execution at any line, in real time rather than 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. There is no debug log to download, no checkpoint to set up, and no org round-trip.
# Set a breakpoint in VS Code, then click
# "Renmy: Debug Test" above the test method,
# or press F5 with a launch configuration.
# 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. renmy runtime 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 rather than 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 renmy runtime Dev UI. Five verbosity levels let you zoom from high-level test flow down to every single evaluation step.
# Run tests with tracing enabled
renmy 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
renmy 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 rather than 30 minutes later.
Watch mode uses the renmy 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
renmy test:watch
# renmy watches your package directories
# 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. The view redraws with the result
#
# Keys while it runs:
# [r] Re-run
# [o] Full errors
# [w] Show watched dirs
# [q] QuitLine coverage tells you what code ran. Mutation testing tells you if your tests actually verified anything. renmy runtime 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.
renmy mutate runs your full test suite against each mutation. In Java, PITest does this. In JavaScript, Stryker. In Python, mutmut. renmy runtime brings mutation testing to Apex, to our knowledge 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. renmy runtime keeps the agent loop fast.
Because renmy 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 renmy:
# 1. Agent generates or modifies an Apex class
# → AccountTriggerHandler.cls updated
# 2. Agent runs tests locally
renmy test "AccountTriggerTest.*" --json
# 3. Agent reads JSON results, spots the failure:
# "Expected 'Technology', got null"
# 4. Agent fixes the handler, runs tests again
renmy test "AccountTriggerTest.*" --json
# 5. All green - agent moves to next task
# Total elapsed: ~15 seconds
# With org testing: ~15 minutesPoint renmy at any SFDX project and run your tests locally in seconds.