Comparison

Nimbus vs ApexMocks

ApexMocks (part of fflib) gave Apex devs fast tests by stubbing out the database. Nimbus achieves the same speed by running a real database locally. They're not mutually exclusive — Nimbus implements the Stub API, so your existing ApexMocks tests run unchanged.

Short version

Keep your ApexMocks tests — they run on Nimbus without changes. New tests can go either way: mock when you want to isolate a single class, real DB when you want to verify SOQL, triggers, and DML actually behave. Same millisecond speed for both. No more "tests pass but production breaks" surprises.

What each tool is, and what it isn't

ApexMocks (fflib_ApexMocks)

A mocking framework built on Salesforce's Stub API. You write a unit selector, you stub its return value, you assert your code called the right method with the right arguments. Tests don't touch a database; they don't need org connectivity for the mocked path; they run fast.

What it costs: every test that mocks SOQL or DML is testing your code's relationship with the mock, not with the actual platform. If your trigger logic depends on a chained recalculation, an Order-of-Execution behaviour, or a SOQL operator you haven't seen yet, the mock won't surface the bug. The classic failure mode: 95% coverage, all green, deploy breaks anyway.

Nimbus

Nimbus runs your Apex against an embedded PostgreSQL. SOQL is translated to real SQL. DML actually persists. Triggers fire on insert/update/delete the same way the platform fires them. Each test runs in an isolated transaction that's rolled back at the end. No mocks required — but the Stub API is fully implemented, so when you do want isolated unit tests, mocks still work.

The compatibility line: ApexMocks tests run on Nimbus, unchanged

Nimbus implements System.Test.createStub with the same semantics the platform uses. Anything built on top — ApexMocks, custom stub frameworks, your team's internal harness — works the same way. The same fflib_ApexMocks mocks = new fflib_ApexMocks(); line that ran on the platform yesterday runs on Nimbus today.

apex
@isTest
static void doesWork() {
  fflib_ApexMocks mocks = new fflib_ApexMocks();
  IAccountSelector mockSelector = (IAccountSelector) mocks.mock(IAccountSelector.class);
  mocks.startStubbing();
  mocks.when(mockSelector.selectById(new Set<Id>{ acctId }))
       .thenReturn(new List<Account>{ new Account(Id = acctId, Name = 'Stubbed') });
  mocks.stopStubbing();

  Application.selector.setMock(mockSelector);
  // ... rest of the test
}
// Runs unchanged on Nimbus.

When to mock, when to run against the real DB

With Nimbus you stop having to pick on the basis of speed — both run in milliseconds. The question becomes what are you actually testing:

  • Mock when the test cares about the contract between two classes — "did the service call the selector with these arguments?" Mocks make that assertion direct.
  • Real DB when the test cares about what data ends up in the database — "after this code runs, the Account's AnnualRevenue is updated and the related Contact count is correct." Mocks here just rebuild the database in your head; the real DB is the database.
  • Real DB for trigger logic, every time. Triggers fire on DML — stub the DML and you stub out the trigger.
  • Real DB for SOQL operators you're unsure about. STDDEV, GROUP BY ROLLUP, polymorphic relationships — running them locally tells you the truth.

Migrating gradually

You don't rip out ApexMocks to adopt Nimbus. You install Nimbus, point it at the same repo, and run the existing test suite. Everything that was green stays green. New tests can be written either way — pick per-test, not as a global decision.

Common pattern: leave existing mocks in place for stable code, write new tests against the real DB. Over time the test suite drifts toward "real DB by default, mocks where isolation matters." The decisions stop being about speed and become about clarity.

Try your existing fflib suite on Nimbus

If your project uses ApexMocks, the fastest signal is to run your current test suite through Nimbus and watch the pass rate. Anything that was green on the platform should be green locally. If it isn't, the difference tells you exactly where mocks were hiding something — usually a real bug worth fixing.