Skip to content

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 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 StaticResourceCalloutMock returns it. MultiStaticResourceCalloutMock returns 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:

@IsTest
public 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.

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 200 with a normal body: the classes fill, the values are right.
  • A 404, and a 400: your code treats them as the caller’s problem, logs them for a person, and doesn’t retry.
  • A 500 or a 503: 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.

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.

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.

A mock, or the real call A mock, or the real call: two kinds of test. Top row: a unit test, with Test.setMock, which counts toward the 75% coverage, calls out and is answered by a mock playing Redrock, any status and any body you choose, never through the named credential; it proves your code handles every answer: 200, 404, 500, a strange body, no answer. Bottom row: an Apex integration test, in amber and tagged Developer Preview, marked @IntegrationTest, in a scratch org only, with no coverage, calls out to Redrock's test environment, the real call through the named credential; it proves the real call works at all: the address, the sign-in, today's answer. The legend: amber is a Developer Preview in Winter '27 and can change. Underneath: a mock answers exactly what you told it; it can't tell you the named credential is broken. {"author": "Steve Simpson", "blueprint": "1.0.0", "builder": "standard 1.57.1", "built": "2026-10-31", "id": "DGM-INT-SC-07", "rights": "\u00a9 2026 Twinshark LLC. All rights reserved.", "template": "cells 0.5.0", "theme": "stevetecharc 1.4.0"} THE TEST WHAT ANSWERS IT WHAT IT PROVES A unit test Test.setMock; counts toward the 75% A mock playing Redrock any status, any body you choose; never the named credential Your code handles every answer 200, 404, 500, a strange body, no answer An Apex integration test @IntegrationTest; scratch org only; no coverage Developer Preview Redrock's test environment the real call, through the named credential The real call works at all the address, the sign-in, today's answer calls out proves calls out proves A mock answers exactly what you told it. It can't tell you the named credential is broken. HOW TO READ IT Developer Preview in Winter '27: can change © 2026 Twinshark LLC · stevetecharc.com
Click to enlarge

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.

  • 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.