Skip to main content

SF-9108 · Coding · Medium

How do you mock callouts in tests?

✓ Verified by Vikas Singhal · Last reviewed 5/17/2026 · Updated for Spring '26

Apex tests run in a sandboxed transaction that forbids real HTTP traffic. Try to call out and the runtime throws You have uncommitted work pending. Please commit or rollback before calling out. To test code that makes callouts, you replace the network layer with a mock that returns canned responses.

The two mock interfaces

InterfaceUse for
HttpCalloutMockREST callouts (anything using the Http class)
WebServiceMockSOAP callouts generated by WSDL2Apex

HttpCalloutMock — the common case

@isTest
public class WeatherServiceMock implements HttpCalloutMock {
    public HttpResponse respond(HttpRequest req) {
        HttpResponse res = new HttpResponse();
        res.setHeader('Content-Type', 'application/json');
        res.setBody('{"temp_c": 21.5, "city": "Bengaluru"}');
        res.setStatusCode(200);
        return res;
    }
}

@isTest
static void getTempC_returnsParsedTemp() {
    Test.setMock(HttpCalloutMock.class, new WeatherServiceMock());

    Test.startTest();
    Decimal temp = WeatherService.getTempC('Bengaluru');
    Test.stopTest();

    System.assertEquals(21.5, temp);
}

The line Test.setMock(HttpCalloutMock.class, new WeatherServiceMock()) tells the platform: “for the rest of this test, instead of sending HTTP requests, route them through my mock’s respond() method.”

Making the mock smart

Real services return different responses for different inputs. A mock that always returns the same JSON only tests the happy path. Inspect the request:

public HttpResponse respond(HttpRequest req) {
    HttpResponse res = new HttpResponse();
    if (req.getEndpoint().contains('city=Mumbai')) {
        res.setStatusCode(200);
        res.setBody('{"temp_c": 32.1}');
    } else if (req.getEndpoint().contains('city=INVALID')) {
        res.setStatusCode(404);
        res.setBody('{"error": "City not found"}');
    } else {
        res.setStatusCode(500);
    }
    return res;
}

Now one mock class covers success, not-found, and server-error paths across multiple test methods.

Multi-callout mock (HttpCalloutMock + counter)

A single test method might make several callouts in sequence (chained API calls, retry logic). Use an instance variable to step through responses:

public class MultiResponseMock implements HttpCalloutMock {
    Integer callIndex = 0;
    List<HttpResponse> responses;
    public MultiResponseMock(List<HttpResponse> r) { responses = r; }
    public HttpResponse respond(HttpRequest req) {
        return responses[callIndex++];
    }
}

Static resources for big payloads

If your mock needs to return a 50 KB JSON blob, don’t embed it as a String literal. Upload it as a Static Resource and use StaticResourceCalloutMock:

StaticResourceCalloutMock mock = new StaticResourceCalloutMock();
mock.setStaticResource(WeatherSampleResponse); // Static Resource name
mock.setStatusCode(200);
mock.setHeader('Content-Type', 'application/json');
Test.setMock(HttpCalloutMock.class, mock);

WebServiceMock — for SOAP

WSDL2Apex generates classes that don’t go through the Http class, so HttpCalloutMock doesn’t intercept them. Use WebServiceMock instead:

public class CreditCheckMock implements WebServiceMock {
    public void doInvoke(Object stub, Object request, Map<String, Object> response,
                         String endpoint, String soapAction, String requestName,
                         String responseNS, String responseName, String responseType) {
        CreditCheck.CreditResponse r = new CreditCheck.CreditResponse();
        r.score = 750;
        response.put('response_x', r);
    }
}

Things that bite people

  • Forgetting Test.setMock before the call. The mock has to be registered before the code under test runs, not after.
  • Mock annotated @isTest so it doesn’t count toward org code limits. Optional but tidy.
  • Endpoint must still be valid syntactically — the mock is invoked, but the endpoint URL has to parse. If you pass garbage, you’ll see a URL exception before the mock is reached.

Verified against: Apex Developer Guide — Testing HTTP Callouts. Last reviewed 2026-05-17 for Spring ‘26 release.