Skip to content

Mocking in Apex: Stub API, HttpCalloutMock and ApexMocks Compared

Four ways to isolate Apex under test, from the built-in callout mocks through to fflib ApexMocks, with what each one costs and the design each one demands.

This article is part of the Apex Testing series, three articles on writing Apex tests that catch regressions instead of chasing a number. Articles are standalone; no need to read in order.

TL;DR

  • Apex ships with two mocking mechanisms and they cost nothing: Test.setMock for HTTP and SOAP callouts, and the Stub API (Test.createStub plus System.StubProvider) for your own classes.
  • The Stub API works on concrete classes, not just interfaces, which is unusual and useful. It cannot touch static methods, private methods, constructors, or sObjects.
  • @TestVisible static injection is dependency injection at its smallest: one annotation, no library behind it, and it handles most cases.
  • fflib ApexMocks gives you Mockito-style when/thenReturn and interaction verification. It is better at complex cases and more expensive: it needs a factory, a team that understands it, and an unmanaged dependency you upgrade yourself.
  • Mocked tests run in milliseconds instead of seconds because they do no DML. On a suite of 800 tests that is the difference between a usable pipeline and one people skip.
  • The failure mode of heavy mocking is a suite that verifies your code called the methods you expected, while nothing verifies the system works.

What You'll Learn

  • How to mock a callout with HttpCalloutMock, and the three variants you will actually use
  • How the Apex Stub API actually works, with a complete StubProvider you can copy
  • The dependency injection patterns that make any mocking possible in Apex
  • What ApexMocks buys you over the built-in Stub API, and what it charges
  • A decision table for picking one, and the point where mocking starts doing harm

The Problem

Apex tests are slow because they are not unit tests. Almost every one inserts records, which means DML, which means triggers, flows, validation rules, sharing recalculation, and roll-up summaries, all before the code you actually wanted to test has run.

That has two consequences. The suite gets slow enough that people stop running it locally, so defects arrive at the pipeline rather than the editor. And the tests get fragile: a test for an invoice calculator starts failing because someone added a required field to Account, which has nothing to do with invoices.

Mocking is how you cut those dependencies. The question is not whether to do it but how much machinery to take on, because Apex offers four quite different answers at very different prices.

Common questions this article answers:

  • How do I test a class that makes an HTTP callout?
  • Do I need a mocking library, or is the built-in Stub API enough?
  • What does ApexMocks give me that Test.createStub does not?

Quick Answer

For callouts, use the built-in mocks: implement HttpCalloutMock (or use StaticResourceCalloutMock to hold the response body in a static resource) and register it with Test.setMock(HttpCalloutMock.class, new MyMock()) before the code under test runs. For your own classes, the Apex Stub API is the native option: implement System.StubProvider to decide what each intercepted call returns, then build the stand-in with Test.createStub(MyService.class, new MyStubProvider()). It works on concrete classes as well as interfaces, so you do not have to introduce an interface for every dependency, but it cannot stub static methods, private methods, constructors, or sObjects. Either approach needs a way to get the stand-in into the class under test, which in Apex is usually a @TestVisible private static field holding the dependency that the test reassigns. Reach for fflib ApexMocks when you want Mockito-style stubbing with argument matchers and verification of how many times something was called, and when you already have the fflib Application factory to inject through. If you do not have that factory, ApexMocks will cost more than it returns.

Mocking callouts: the part with no alternative

Apex refuses to make a real callout from a test. Attempt it and you get System.CalloutException: Methods defined as TestMethod do not support Web service callouts. This is the one case where mocking is not a design choice, it is the only route.

The interface has a single method:

@IsTest
public class ExchangeRateCalloutMock implements HttpCalloutMock {

    private final Integer statusCode;
    private final String body;

    public ExchangeRateCalloutMock(Integer statusCode, String body) {
        this.statusCode = statusCode;
        this.body = body;
    }

    public HttpResponse respond(HttpRequest request) {
        // Assert on the request itself: this is where you verify the caller
        // built the right URL, method and headers.
        Assert.isTrue(
            request.getEndpoint().contains('/v1/rates'),
            'Unexpected endpoint: ' + request.getEndpoint()
        );
        Assert.areEqual('GET', request.getMethod(), 'Rate lookup should be a GET');

        HttpResponse response = new HttpResponse();
        response.setStatusCode(statusCode);
        response.setHeader('Content-Type', 'application/json');
        response.setBody(body);
        return response;
    }
}

Registering it:

@IsTest
static void rateIsParsedFromTheResponse() {
    Test.setMock(
        HttpCalloutMock.class,
        new ExchangeRateCalloutMock(200, '{"base":"NZD","rates":{"AUD":0.92}}')
    );

    Test.startTest();
    Decimal rate = ExchangeRateService.get('NZD', 'AUD');
    Test.stopTest();

    Assert.areEqual(0.92, rate, 'Rate should be parsed from the rates map');
}

Three points save time here.

Assert inside the mock. The respond method receives the actual HttpRequest. That is your only chance to check that the calling code built the right endpoint, method, headers and body. Most callout tests throw this away and only check the parsed result, which means a class can start calling the wrong URL and every test stays green.

Test the unhappy responses. A 500, a 401, a timeout, a body that is valid JSON but the wrong shape, and an empty body. Write a mock per scenario and a test per mock. Integration defects in Salesforce are far more often "the partner returned a 503 and we wrote null into the record" than "we parsed the happy path wrong".

Use the static resource variants for large bodies. StaticResourceCalloutMock reads the response body from a static resource, and MultiStaticResourceCalloutMock maps different endpoints to different resources. Both keep a 400-line JSON payload out of your Apex, and both let an integration analyst update a fixture without touching code. For SOAP, the equivalent is the WebServiceMock interface with its doInvoke method.

The Apex Stub API

For your own classes, Apex has a native mocking primitive. It is two pieces: an interface you implement to decide what calls return, and a factory method that builds the stand-in.

System.StubProvider has one method, and it receives everything about the intercepted call:

@IsTest
public class SimpleStubProvider implements System.StubProvider {

    // Method name to the value it should return.
    private final Map<String, Object> returns = new Map<String, Object>();
    // Method name to how many times it was called, so tests can verify.
    public final Map<String, Integer> callCounts = new Map<String, Integer>();

    public SimpleStubProvider returning(String methodName, Object value) {
        returns.put(methodName, value);
        return this;
    }

    public Object handleMethodCall(
        Object stubbedObject,
        String stubbedMethodName,
        Type returnType,
        List<Type> listOfParamTypes,
        List<String> listOfParamNames,
        List<Object> listOfArgs
    ) {
        callCounts.put(
            stubbedMethodName,
            (callCounts.get(stubbedMethodName) ?? 0) + 1
        );

        if (!returns.containsKey(stubbedMethodName)) {
            Assert.fail('Unstubbed method called: ' + stubbedMethodName);
        }
        return returns.get(stubbedMethodName);
    }
}

Using it:

@IsTest
static void serviceUsesTheSelectorResult() {
    Account fixture = new Account(
        Id = TestIds.next(Account.SObjectType),
        Name = 'Northwind Trading',
        Industry = 'Technology'
    );

    SimpleStubProvider provider = new SimpleStubProvider()
        .returning('selectById', new List<Account>{ fixture });

    AccountsSelector stub = (AccountsSelector) Test.createStub(
        AccountsSelector.class,
        provider
    );

    // Inject the stand-in (see the next section on how the field gets there).
    AccountService.selector = stub;

    Test.startTest();
    String summary = AccountService.summarise(new Set<Id>{ fixture.Id });
    Test.stopTest();

    Assert.areEqual('Northwind Trading (Technology)', summary, 'Summary format changed');
    Assert.areEqual(1, provider.callCounts.get('selectById'), 'Selector should be queried once');
}

Notice there is no DML anywhere. No Account is inserted, no trigger fires, and the test runs in milliseconds. The Id on the fixture is a synthetic one, which you can generate without inserting anything.

What makes the Stub API unusual is that it stubs concrete classes, not only interfaces. Most mocking frameworks in other languages need an interface or a virtual method to hook into. Here you can pass AccountsSelector.class directly even if nothing implements anything. That removes a large amount of the ceremony people associate with mocking.

The limits are real, and they are where the design pressure comes from:

Cannot be stubbed Why it matters
Static methods A utility class of statics is untestable in isolation. This is the main reason to prefer instance methods for anything with a dependency.
Private and protected methods Only public and global methods are intercepted. Private behaviour is tested through the public surface, which is the right answer anyway.
Constructors The stand-in is built by the platform, so any work in the constructor does not run. Keep constructors free of side effects.
sObjects You cannot stub Account. Build a real in-memory sObject instead, which is cheap since no insert is needed.
System types You cannot stub Database, Http, or similar. Wrap them in a thin class of your own and stub that.
Iterators Batch Iterable implementations need a different approach.

Test.createStub only works in a test context, so none of this leaks into production code.

Getting the stand-in into the class

A mock is useless if the class under test builds its own dependency with new. Apex has no dependency injection container in the platform, so you need a seam. Three, in increasing cost:

A @TestVisible static field. The smallest thing that works, and where most teams should start:

public with sharing class AccountService {

    @TestVisible
    private static AccountsSelector selector = new AccountsSelector();

    public static String summarise(Set<Id> accountIds) {
        List<Account> accounts = selector.selectById(accountIds);
        // ...
    }
}

Production code sees a private field it cannot touch. Tests can assign to it. One annotation, no library, no factory.

The cost is static mutable state. A test that reassigns selector and does not restore it can affect a later test in the same transaction, so reset it in a finally or keep each test method to a single assignment. It is a small discipline for a small mechanism.

Constructor injection. Cleaner, and the better fit where the class is already instance based:

public with sharing class AccountService {

    private final AccountsSelector selector;

    public AccountService() {
        this(new AccountsSelector());
    }

    @TestVisible
    private AccountService(AccountsSelector selector) {
        this.selector = selector;
    }
}

No static state, but every caller of the class now sits between you and any change to the constructor signature.

A factory with a test override. This is what fflib's Application class does, and understanding it helps even if you never install fflib:

public class Application {
    public static final SelectorFactory Selector = new SelectorFactory();
    // Selector.newInstance(Account.SObjectType) returns the real selector,
    // unless Selector.setMock(stub) has been called from a test.
}

One place decides what every class gets. Tests call Application.Selector.setMock(stub) and every class downstream receives the stand-in without knowing anything about it. This is the piece that makes large-scale mocking practical, and it is also the piece that makes it a commitment.

ApexMocks, and what it adds

fflib-apex-mocks is a Mockito-style framework maintained by the Apex Enterprise Patterns community. It sits on the Stub API and gives you a much more expressive surface:

@IsTest
static void invoiceIsRegisteredForCommit() {
    fflib_ApexMocks mocks = new fflib_ApexMocks();

    // Mocks of an interface and of the unit of work.
    IAccountsSelector selector =
        (IAccountsSelector) mocks.mock(AccountsSelector.class);
    fflib_ISObjectUnitOfWork uow =
        (fflib_ISObjectUnitOfWork) mocks.mock(fflib_SObjectUnitOfWork.class);

    Account fixture = new Account(Id = fflib_IDGenerator.generate(Account.SObjectType));

    mocks.startStubbing();
    mocks.when(selector.sObjectType()).thenReturn(Account.SObjectType);
    mocks.when(selector.selectById(new Set<Id>{ fixture.Id }))
         .thenReturn(new List<Account>{ fixture });
    mocks.stopStubbing();

    Application.Selector.setMock(selector);
    Application.UnitOfWork.setMock(uow);

    Test.startTest();
    InvoiceService.raiseFor(new Set<Id>{ fixture.Id });
    Test.stopTest();

    // Verification: not just what came back, but what the code did.
    ((fflib_ISObjectUnitOfWork) mocks.verify(uow, 1))
        .registerNew(fflib_Match.sObjectWith(
            new Map<SObjectField, Object>{ Invoice__c.Account__c => fixture.Id }
        ));
    ((fflib_ISObjectUnitOfWork) mocks.verify(uow, 0)).commitWork();
}

Three things here are not available from the raw Stub API.

Per-argument stubbing. mocks.when(selector.selectById(specificIds)).thenReturn(...) binds a return value to a particular set of arguments. With a hand-rolled StubProvider you get the argument list and have to write that dispatch yourself, which is fine for one method and tiresome for thirty.

Argument matchers. fflib_Match.sObjectWith(...), fflib_Match.anyId(), and the rest let you assert on the shape of what was passed without reconstructing it exactly.

Interaction verification. mocks.verify(uow, 1) asserts the method was called exactly once. mocks.verify(uow, 0) asserts it was never called, which is how you test that a guard clause worked. Hand-rolling call counting is possible, as the callCounts map above shows, but matching on arguments as well gets old quickly.

Comparison

Test.setMock Stub API @TestVisible injection fflib ApexMocks
Cost to adopt None, built in None, built in None, one annotation Deploy and maintain an unmanaged library
Design it demands None Public instance methods A static field per dependency Interfaces plus a factory (usually fflib-apex-common)
Stubs concrete classes Not applicable Yes Yes Yes
Per-argument returns Manual, from the request Manual, from listOfArgs Manual Built in
Interaction verification Manual asserts in respond Manual call counting None Built in, with matchers
Best at Any callout, no alternative exists One or two dependencies per class Small to mid codebases, incremental adoption Layered codebases with many collaborators per class
Main risk Mock diverges from the real API Provider grows into a hand-rolled framework Static state leaking between tests Tests that assert on call sequence rather than behaviour

Test.setMock is not optional, so use it. The Stub API plus @TestVisible covers most teams, and costs nothing to try on one class this week. ApexMocks pays off when you already have the layered architecture it assumes, and is a net loss when you do not, because you end up maintaining a factory and an unmanaged library in order to mock three classes.

Where mocking starts doing harm

Two failure modes show up in codebases that adopted mocking enthusiastically.

Tests that assert on interactions rather than outcomes. A suite full of verify(uow, 1).registerNew(...) proves your code called the methods you expected in the order you expected. Rename a method or move work between two collaborators and every test breaks, even though the behaviour never changed. That is a test suite that opposes refactoring instead of enabling it. Verification is valuable where the interaction is the behaviour, such as proving commitWork() is not called on a validation failure. It is a liability everywhere else.

A suite with no integration left. Mock the selector, mock the unit of work, mock the callout, and nothing in the suite ever touches the database. Then a validation rule fires in production on a field nobody's test ever populated. Keep a deliberate layer of tests that do real DML on the critical paths, and let mocking take the volume elsewhere. Usually that means a small number of slow tests that prove the system works end to end, and a large number of fast ones that prove each piece is right.

A related trap: reaching for a mocking framework to get at a private method is the wrong instinct in any language, and the same argument applies in Java with PowerMock: if a private method is complex enough to need its own test, it usually wants to be a public method on a class of its own.

Frequently Asked Questions

Q: Do I need an interface for every class I want to stub?

A: No. Test.createStub accepts a concrete class type, so Test.createStub(AccountsSelector.class, provider) works with no interface anywhere. Interfaces are still useful when you have more than one real implementation, or when you want the dependency's contract to be visible in one small file, and fflib's patterns assume them throughout. But if the only reason you were about to write IAccountsSelector was to enable mocking, you can skip it.

Q: Why does my stub return null instead of the value I set?

A: Usually one of three things. The method is static, private or protected, in which case the Stub API never intercepts it and the real implementation runs. The class under test built its own dependency with new rather than taking yours, so your stand-in is not in the picture at all. Or your StubProvider is matching on a method name that does not match the actual name, for example because of an overload. Adding an Assert.fail('Unstubbed method called: ' + stubbedMethodName) to the provider, as in the example above, turns all three from a silent null into a clear failure.

Q: Is ApexMocks safe to use in a production org?

A: Yes, in the sense that it is widely used and its test-only classes carry @IsTest so they do not count against your code coverage or your Apex character limit in the way normal classes do. The real question is ownership: it is an open-source unmanaged package, so you deploy the source into your org and you are responsible for upgrading it, reconciling it with your own naming, and fixing anything that breaks in a release. That is a reasonable cost for a team already running the Apex Enterprise Patterns layers, and an unreasonable one for a team that is not.

Q: How do I generate a fake record Id without inserting anything?

A: fflib ships fflib_IDGenerator.generate(Account.SObjectType). Without fflib, a short utility that takes the three-character key prefix from SObjectType.getDescribe().getKeyPrefix() and pads a counter to fifteen characters does the same job in about ten lines. Either way the point is the same: a mocked test needs an Id so the code under test can key maps on it, but it does not need the record to exist.

Q: Should I mock in a test that also does DML?

A: Mixing is fine and often sensible. A test that inserts real Accounts because it is testing a trigger, while mocking the callout that trigger makes, is exactly right. What is not fine is mocking the thing you are trying to test. If the assertion would still pass when the class under test was replaced with an empty method, the test is testing the mock.

Key Takeaways

  • Callouts have one answer: Test.setMock with HttpCalloutMock, StaticResourceCalloutMock or WebServiceMock, and assert on the request inside the mock.
  • The Stub API is free and stubs concrete classes: no interface required, but nothing static, private or constructor based can be intercepted.
  • The injection seam matters more than the mocking tool: a @TestVisible private static field gets most teams most of the way for the price of one annotation.
  • ApexMocks pays for itself only with the architecture it assumes: interfaces and a factory. Without those, it adds a dependency and a learning curve to solve a smaller problem.
  • Speed is the main payoff: tests with no DML run in milliseconds, which keeps a large suite runnable on a developer's machine.
  • Verify behaviour, not choreography: asserting on call counts everywhere produces a suite that breaks on every refactor and catches nothing.

What's Next?

Recommended reading:

Action items:

  1. Find your slowest test class and count how many records it inserts to test one method. That number is your case for mocking.
  2. Pick one class with a single dependency, add a @TestVisible private static field, and write one Stub API test against it. Measure the runtime before and after.
  3. Audit your callout mocks: do any of them assert on the request, or do they all just return a body?
  4. Before installing ApexMocks, check whether you have a factory to inject through. If not, that is the piece to build or decide against first.

Resources & References

Responses

Checking your session.

Loading responses.