Testing Callouts
Module 8 of 8 in the chapter Salesforce as an API Consumer, chapter 9 of the Integration Architect course. Each module stands on its own. Previous: Callbacks and Outbound Messages
The question this module answers: How do you test code that calls another system, when Apex tests can’t call it, and what does a test with a fake answer still miss?
Apex code doesn’t go to production without tests: at least 75% of it has to be covered before you can deploy. And Apex tests can’t make real callouts. “By default, test methods don’t support HTTP callouts, so tests that perform callouts fail” (Testing HTTP Callouts (opens in a new tab), Apex Developer Guide, checked 2026-10-31). So in a test, something has to play the other system.
A Mock Plays the Other System
Section titled “A Mock Plays the Other System”A mock is a fake answer you control. You tell the test, with Test.setMock, that any callout should get your answer instead of going out. Salesforce gives you three ways to write one:
HttpCalloutMock: a small class with one method that receives the request and returns whatever response you build: any status, any headers, any body.- A static resource: put the sample answer in a file, and
StaticResourceCalloutMockreturns it.MultiStaticResourceCalloutMockreturns a different file for each address, for code that makes several calls. WebServiceMock: the same idea for a SOAP callout generated from a WSDL.
Here’s a mock that plays Redrock, answering the shipment call from module 2:
@IsTestpublic class RedrockShipmentMock implements HttpCalloutMock { private Integer status; private String body; public RedrockShipmentMock(Integer status, String body) { this.status = status; this.body = body; } public HttpResponse respond(HttpRequest req) { HttpResponse res = new HttpResponse(); res.setStatusCode(status); res.setHeader('Content-Type', 'application/json'); res.setBody(body); return res; }}Because you choose the status and the body, one mock can play every answer Redrock could give.
Make It Fail on Purpose
Section titled “Make It Fail on Purpose”The happy path is the easy test, and the least useful. Module 3 said what your code should do for each kind of answer. The tests are where you prove it does:
- A
200with a normal body: the classes fill, the values are right. - A
404, and a400: your code treats them as the caller’s problem, logs them for a person, and doesn’t retry. - A
500or a503: your code treats it as their problem, and retries or hands off as designed. - A body you didn’t expect: XML instead of JSON, a missing field, an empty body. Your code fails cleanly instead of crashing.
- No answer at all: have the mock throw a
CalloutException, the way a timeout would, and check what the user or the log sees.
Use made-up data in every mock. A test that’s copied a real response, with a real customer’s name in it, has put that customer into your code repository forever.
Call, Then Write, in Tests Too
Section titled “Call, Then Write, in Tests Too”Module 5’s rule applies to tests. If the test inserts data first and then the code calls out, the test fails with uncommitted work pending. Salesforce’s way around it: put the callout between Test.startTest() and Test.stopTest(), with Test.setMock after Test.startTest, and keep the inserts outside that block (Performing DML Operations and Mock Callouts (opens in a new tab), checked 2026-10-31). Watch your start test and stop test when you’re doing your assertions.
What a Mock Can’t Catch
Section titled “What a Mock Can’t Catch”Every one of those tests can pass while production fails. The mock never goes through the named credential, never signs in, never meets the real Redrock. If the named credential points at the wrong address, if the token setup is broken, or if Redrock changed its answer last week, the mock doesn’t know. It answers exactly what you told it to.
Salesforce says so plainly: “Unit tests can’t make real callouts, so a mock can hide a broken named credential or payload.” Its answer, new in Winter ‘27, is the Apex integration test, in Developer Preview. “Integration tests let you call the real service from a scratch org. They aren’t available in production orgs or sandboxes, and they don’t count toward code coverage.” You turn on the feature in the scratch org’s definition and mark the test class and methods @IntegrationTest (The Salesforce Developer’s Guide to the Winter ‘27 Release (opens in a new tab), checked 2026-10-31).
Read what that means. It’s a preview, so it can change. It runs only in scratch orgs, against a real service, so you need a test version of the other system to call: Redrock’s test environment, never its production. And it doesn’t help your coverage, so you still need the mocks. The two do different jobs: mocks prove your code handles every answer; an integration test proves the real call works at all.
Two kinds of test, two different questions.
Testing across the whole integration, both systems and the layer between them, is bigger than Apex. Chapter 14 covers end-to-end testing and checking each side against the contract.
What an Architect Decides Here
Section titled “What an Architect Decides Here”- The answers every callout is tested against: success, the caller’s errors, their errors, a strange body, no answer. Written down, then tested.
- Where the sample answers come from: made up, never copied from production.
- How the real call gets tested: an integration test against the other system’s test environment, a test by hand on the lab bench, or both, before go-live and after every change to the named credential.
- Who owns the other system’s test environment, and whether it exists at all.
Explore
Section titled “Explore”- Testing HTTP Callouts (opens in a new tab), Testing HTTP Callouts by Implementing the HttpCalloutMock Interface (opens in a new tab) and Performing DML Operations and Mock Callouts (opens in a new tab): Apex Developer Guide (checked 2026-10-31)
- The Salesforce Developer’s Guide to the Winter ‘27 Release (opens in a new tab): Salesforce Developers Blog, Apex integration tests (Developer Preview)
