HttpCalloutMock, you return a scripted response that you control. You use the exact same strategy for internal code dependencies using StubProvider. You are testing your own logic, not the uptime of an external system.
๐ Key Points
- Apex completely blocks real outbound HTTP callouts during unit test execution.
HttpCalloutMockis used to intercept callouts at the transport layer and return a fakeHttpResponse.StubProvideris used to mock Apex dependencies at the object layer, allowing you to isolate and test specific business logic.- Mocking allows you to safely and predictably test success paths, error states (like 404 or 500 errors), and timeouts.
- You must set your mock using
Test.setMock()before the callout occurs in your test method.
Imagine writing a test for a payment processing flow that attempts to ping a real external payment gateway.
The Old/Bad Way: Without a mock, the test either crashes (since callouts are blocked in tests) or the developer deliberately avoids testing the callout path. The result? The first time a declined credit card triggers a failure path is in production, affecting real users.
The New/Good Way:
- Create an
HttpCalloutMockclass that returns a carefully scriptedHttpResponse. - Use
Test.setMock(HttpCalloutMock.class, new PaymentMock(200))for your success path. - Add a second test utilizing a mock that returns a
402 Payment Requiredstatus to purposefully validate your failure handling.
Concept: Layers of Mocking
Because tests cannot make real network requests, Salesforce provides HttpCalloutMock (and WebServiceMock for SOAP) to intercept HTTP traffic at the transport layer. You tell the test context to use your mock by calling Test.setMock(), which then intercepts the request and hands back your predetermined response.
- Transport Layer: Use
HttpCalloutMockto validate how your code constructs a request (endpoints, headers, body) and parses a response. - Object Layer: Use
StubProviderto mock Apex dependencies. This validates what your business logic actually does with the parsed results, without triggering the underlying database or network layers. - The Trap: Conflating the two layers leads to brittle tests that are overly focused on plumbing rather than behavior.
- Rule:
HttpCalloutMockfakes the wire.StubProviderfakes an Apex class. Pick your tool based on what you need to replace. - Gain: You can write tests for failure shapes that are impossible to trigger reliably on demand (e.g., server timeouts, 500 Internal Server errors, or retries that only succeed on the second attempt).
- Reach For:
HttpCalloutMockwhen your code makes a callout.StubProviderwhen you need to fake a class behavior. If interacting with a managed package, wrap the package class behind your own interface first, as you cannot stub what you cannot see. - Price: A mock permanently encodes your assumptions about the external system. If the third-party API contract changes, your green tests will lie to you because they are still passing against outdated assumptions.
- Limits: Tests cannot make real callouts. Interception only happens at the transport layer.
- At Volume: Make your mocks programmable (passing a scripted list of responses via the constructor) so you don't have to write a separate mock class for every single scenario.
Core Q&A
HttpCalloutMock for success, error, and timeout shapes.
A: Break it down into three tiers:
- Tier One:
HttpCalloutMocktests for the API client. Assert on the request it builds (endpoint, headers, body) and ensure it correctly parses successes, errors, timeouts, and malformed JSON into typed results. (UseMultiStaticResourceCalloutMockfor multi-call sequences). - Tier Two: Logic tests that inject a stubbed client returning typed results. Assert the discount application across boundary cases with zero HTTP plumbing involved.
- Tier Three: One integration-shaped test that wires the real service to the transport mock from end to end. Diagnostic precision is the goal here—each tier should only fail for one specific reason.
// 1. A fake HTTP service. Tests can never make a real callout.
@isTest
class PricingMock implements HttpCalloutMock {
public HttpResponse respond(HttpRequest req) {
// 2. Check the code called the RIGHT endpoint.
System.assert(req.getEndpoint().contains('/v2/price'));
HttpResponse res = new HttpResponse();
// 3. Script the reply. Change this to 500 to test the failure path.
res.setStatusCode(200);
res.setBody('{"discount": 12.5}');
return res;
}
}
// Inside your test method:
Test.setMock(HttpCalloutMock.class, new PricingMock());
HttpCalloutMock that only returns a 200 OK status gives you a false sense of security. Always write negative tests that return 401s, 500s, or throw a CalloutException to ensure your Apex code doesn't crash when the external system goes down.
A: Make the mock programmable. Pass a scripted list of responses and behaviors into its constructor.
- Throw a
CalloutExceptionto simulate a timeout. - Return a 500 status code, and then a 200 on the next invocation, to properly exercise a retry-to-success path.
- Have the mock record its own invocation count. This allows your test to assert, "The system retried exactly 3 times before moving the record to a dead-letter queue."
- If your retry logic lives in a
Queueablechain, useTest.startTest()andTest.stopTest()to force asynchronous execution. Then assert against the logs or records that theFinalizerwrote.
A: Wrap it. Define your own Apex interface that mirrors just the slice of the managed package API that you actually use.
- Write a production implementation of this interface that delegates the actual work to the package. Then, inject the interface.
- Your unit tests can now easily stub the wrapper.
- The thin adapter class remains as the only glue code lacking unit test coverage—cover it with an integration test instead.
- This identical pattern handles other unstubbable elements like static methods and standard System classes.
- Bonus Insight: The wrapper pattern not only enables testability but also insulates your core codebase from unexpected changes in the managed package's API.
A: When your code hits multiple different endpoints in a single run, a standard mock will struggle because it needs to know which response to give. You have two main options:
- Custom Routing Mock: Write a custom
HttpCalloutMockthat inspectsreq.getEndpoint()and usesif/elseor aswitchstatement to return the correct response for each specific URL. - MultiStaticResourceCalloutMock: Use this built-in Salesforce class. It allows you to map specific endpoints directly to different Static Resources containing your mock JSON responses, completely eliminating the need for complex custom routing logic.
A: HttpCalloutMock fakes the WIRE. It returns the HTTP response your callout code will see. StubProvider fakes a CLASS. It stands in for any dependency hiding behind an interface.
- Testing callout transport code? Mock the wire.
- Testing complex business logic that uses an injected dependency? Stub the class.